summaryrefslogtreecommitdiff
path: root/src/main.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.rs')
-rw-r--r--src/main.rs231
1 files changed, 44 insertions, 187 deletions
diff --git a/src/main.rs b/src/main.rs
index 49ffcfb..afb6e77 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,19 @@
mod parser;
+mod version;
+mod docker;
+mod manifest;
+
use crate::parser::*;
+use crate::version::{VersionPattern, CompareOutcome};
+use crate::docker::DockerRef;
+use crate::manifest::ManifestFile;
use base64::prelude::*;
use rand::distr::{Alphanumeric, SampleString};
use rusqlite::Connection;
-use yaml_rust2::parser::Parser;
-use yaml_rust2::Event;
+use rusqlite::OptionalExtension;
use std::collections::HashMap;
use std::ops::Range;
-use std::sync;
use std::io::Read;
use std::io::Seek;
use std::io::Write;
@@ -194,123 +199,7 @@ struct Repository {
dest: std::path::PathBuf,
}
-enum YContext {
- InDocument,
- InObject,
- InSequence,
- InValue(bool),
-}
-
-#[derive(Debug)]
-struct ManifestFile {
- image_tags: Vec<Range<usize>>,
-}
-
-impl ManifestFile {
- fn parse(content: &str) -> Self {
- let mut yaml = Parser::new_from_str(content);
-
- let mut images = vec!();
- let mut scope = vec!();
-
- loop {
- let (ev, mark) = yaml.next_token().unwrap();
- match ev {
- Event::StreamStart => {}
- Event::StreamEnd => { break; }
- Event::DocumentStart => { scope.push(YContext::InDocument); }
- Event::DocumentEnd => {
- assert!(matches!(scope.pop().unwrap(), YContext::InDocument));
- scope.pop_if(|x| matches!(x, YContext::InValue(_)));
- },
- Event::MappingStart(_, _) => { scope.push(YContext::InObject); },
- Event::MappingEnd => {
- assert!(matches!(scope.pop().unwrap(), YContext::InObject));
- scope.pop_if(|x| matches!(x, YContext::InValue(_)));
- },
- Event::SequenceStart(_, _) => { scope.push(YContext::InSequence); },
- Event::SequenceEnd => {
- assert!(matches!(scope.pop().unwrap(), YContext::InSequence));
- scope.pop_if(|x| matches!(x, YContext::InValue(_)));
- },
-
- Event::Scalar(ref txt, _, _, _) => {
- let parent = scope.last().unwrap();
- match parent {
- YContext::InObject => {
- // We are the key of a mapping, which means the next even is the value
- scope.push(YContext::InValue(txt == "image"));
- },
- YContext::InSequence => {},
- YContext::InValue(img) => {
- if *img {
- images.push(mark.index()..mark.index() + txt.len());
- }
- scope.pop();
- },
- // This should only happen for entirely empty documents
- YContext::InDocument => assert!(txt == ""),
-
- _ => panic!(),
- }
- },
- x => todo!("{:?}", x),
- }
- }
-
- return Self{
- image_tags: images,
- };
- }
-}
-
-#[derive(Debug, Clone)]
-struct DockerRef {
- registry: Option<Range<usize>>,
- image: Range<usize>,
- tag: Option<Range<usize>>,
- digest: Option<Range<usize>>,
-}
-
-impl DockerRef{
- fn parse(content: &str, chunk: &Range<usize>) -> DockerRef {
- let mut string_range = chunk.clone();
-
- let mut digest = None;
- if let Some(idx) = content[string_range.clone()].rfind("@") {
- digest = Some(string_range.start+idx+1..string_range.end);
- string_range.end = string_range.start+idx;
- }
-
- let mut tag = None;
- if let Some(idx) = content[string_range.clone()].rfind(":") {
- tag = Some(string_range.start+idx+1..string_range.end);
- string_range.end = string_range.start+idx;
- }
-
- let mut registry = None;
- let image;
- if let Some(idx) = content[string_range.clone()].find("/") {
- let head = &content[string_range.clone()][..idx];
- if head.contains(":") || head.contains(".") {
- registry = Some(string_range.start..string_range.start+idx);
- image = string_range.start+idx+1..string_range.end;
- } else {
- registry = None;
- image = string_range;
- }
- } else {
- image = string_range;
- }
- return DockerRef {
- registry,
- image,
- tag,
- digest,
- };
- }
-}
#[derive(Debug)]
struct FilePatch {
@@ -318,84 +207,34 @@ struct FilePatch {
content: String,
}
-#[derive(Debug)]
-enum VersionPart {
- String(String),
- Number(u64),
- Hash,
-}
-
-#[derive(Debug)]
-struct VersionPattern {
- parts: Vec<VersionPart>,
-}
-
-#[derive(Debug)]
-enum CompareOutcome {
- Higher,
- Lower,
-
- Incompatible,
- Identical,
-}
-
-impl VersionPattern {
- fn parse(tag: &str) -> Self {
- static RE: sync::LazyLock<regex::Regex> = sync::LazyLock::new(|| regex::Regex::new(r"(?<hash>[a-f0-9]{32})|(?<str>[^0-9]+)|(?<num>[0-9]+)").unwrap());
- let mut parts = vec![];
- for it in RE.captures_iter(tag) {
- if let Some(x) = it.name("str") {
- parts.push(VersionPart::String(x.as_str().to_string()));
- } else if let Some(x) = it.name("num") {
- parts.push(VersionPart::Number(x.as_str().parse().unwrap()));
- } else if let Some(_) = it.name("hash") {
- parts.push(VersionPart::Hash);
- }
- }
-
- return VersionPattern {
- parts,
- }
- }
-
- fn compare(&self, other: &Self) -> CompareOutcome {
- if self.parts.len() != other.parts.len() {
- return CompareOutcome::Incompatible;
- }
-
- let mut state = CompareOutcome::Identical;
- for (self_part, other_part) in self.parts.iter().zip(other.parts.iter()) {
- match (&state, self_part, other_part) {
- (_, VersionPart::String(x1), VersionPart::String(x2)) => if x1 != x2 { return CompareOutcome::Incompatible },
- (_, VersionPart::String(_), _) => return CompareOutcome::Incompatible,
- (CompareOutcome::Identical, VersionPart::Number(x1), VersionPart::Number(x2)) => {
- if x1 > x2 {
- state = CompareOutcome::Lower;
- } else if x1 < x2 {
- state = CompareOutcome::Higher;
- }
- },
- (_, VersionPart::Number(_), VersionPart::Number(_)) => {},
- (_, VersionPart::Number(_), _) => return CompareOutcome::Incompatible,
- (_, VersionPart::Hash, VersionPart::Hash) => {},
- (_, VersionPart::Hash, _) => return CompareOutcome::Incompatible,
- }
- }
-
- return state;
- }
-}
fn update_images(db: &Connection, file: &str, auth: &mut Auth, img: DockerRef, edits: &mut Vec<FilePatch>) -> Result<(), String> {
let registry = img.registry.map(|x| &file[x]).unwrap_or("registry.hub.docker.com");
let mut tag = img.tag.as_ref().map(|x| file[x.clone()].to_string());
+ let image = &file[img.image.clone()];
+
+ let _last_checked = match db.query_one("
+ SELECT last_checked FROM images
+ WHERE registry = ?1 AND image = ?2
+ ", (registry, image), |row| row.get::<_, chrono::DateTime<chrono::Utc>>(0)).optional() {
+ Ok(Some(x)) => x,
+ Ok(None) => {
+ let time = chrono::offset::Utc::now();
+ db.execute("
+ INSERT INTO images(registry, image, last_checked) VALUES (?1, ?2, ?3)
+ ", (registry, &image, time)).unwrap();
+ time
+ },
+ Err(_x) => panic!("Database Failure"),
+ };
+
if let Some(ref tag_str) = tag {
let mut current = VersionPattern::parse(&tag_str);
let mut new_tag = None;
- let mut url = format!("/v2/{}/tags/list", &file[img.image.clone()]);
+ let mut url = format!("/v2/{}/tags/list", &image);
loop {
let mut response = perform_registry_request(registry, &url, "application/vnd.oci.image.index.v1+json", auth).unwrap();
@@ -847,6 +686,24 @@ fn main() {
db.execute("INSERT INTO migrations(id) VALUES (?1)", (4, )).unwrap();
}
+
+ if newest_migration < 5 {
+ db.execute("
+ CREATE TABLE images (
+ id INTEGER PRIMARY KEY NOT NULL,
+ registry TEXT NOT NULL,
+ image TEXT NOT NULL,
+ last_checked DATETIME NOT NULL
+ )
+ ", ()).unwrap();
+
+ db.execute("
+ CREATE UNIQUE INDEX images__registry_image
+ ON images(registry, image)
+ ", ()).unwrap();
+
+ db.execute("INSERT INTO migrations(id) VALUES (?1)", (5, )).unwrap();
+ }
}
let mut repos = vec![];