diff options
| author | Jesper Jensen <jesper@jnsn.dev> | 2025-12-15 07:50:43 +0100 |
|---|---|---|
| committer | Jesper Jensen <jesper@jnsn.dev> | 2025-12-15 07:50:43 +0100 |
| commit | 813916f1729af7b9f1f2b66126e9a23f3bb49113 (patch) | |
| tree | fef4ed00356e0a30e810b0f35394e9e23c3e0fa7 | |
INITIAL COMMIT
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | Cargo.lock | 397 | ||||
| -rw-r--r-- | Cargo.toml | 10 | ||||
| -rw-r--r-- | src/main.rs | 279 |
4 files changed, 687 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..cfa1796 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,397 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "arraydeque" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + +[[package]] +name = "cc" +version = "1.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "foldhash", +] + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "libc" +version = "0.2.178" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "708c0f9d5f54ba0272468c1d306a52c495b31fa155e91bc25371e6df7996908c" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e320a6c5ad31d271ad523dcf3ad13e2767ad8b1cb8f047f75a8aeaf8da139da2" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "tinyjson" +version = "2.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab95735ea2c8fd51154d01e39cf13912a78071c2d89abc49a7ef102a7dd725a" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "version-bumper" +version = "0.1.0" +dependencies = [ + "base64", + "tinyjson", + "ureq", + "yaml-rust2", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "yaml-rust2" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" +dependencies = [ + "arraydeque", + "encoding_rs", + "hashlink", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e8ba899 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "version-bumper" +version = "0.1.0" +edition = "2024" + +[dependencies] +base64 = "0.22.1" +tinyjson = "2.5.1" +ureq = "3.1.4" +yaml-rust2 = "0.10.4" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..fefa4cf --- /dev/null +++ b/src/main.rs @@ -0,0 +1,279 @@ +use base64::prelude::*; +use yaml_rust2::parser::Parser; +use yaml_rust2::Event; +use std::ops::Range; + +fn help(cmd: &str) { + println!( + "{} [options] [--] <FILE> +Search FILE for docker images and suggest updates + +Options: +--auth <USER> <PASS> Authenticate against registry", + cmd + ); +} + +struct Auth { + header: Option<String>, +} + +impl Auth { + fn new() -> Self { + return Auth{ + header: None, + }; + } + + fn parse(&mut self, username: &str, password: &str) { + self.header = Some(format!("Basic {}", BASE64_STANDARD.encode(format!("{}:{}", username, password)))); + } + + fn apply<B>(&self, req: ureq::RequestBuilder<B>) -> ureq::RequestBuilder<B> { + if let Some(header) = &self.header { + return req.header("Authorization", header.clone()); + } else { + return req; + } + } +} + +enum YContext { + InDocument, + InObject, + InSequence, + InValue(bool), +} + +#[derive(Debug)] +struct Chunk { + position: Range<usize>, +} + +fn scan_yaml_for_images<T: Iterator<Item = char>>(mut yaml: Parser<T>) -> Vec<Chunk> { + 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 { + let next_idx = images.len(); + images.push(Chunk{ + position: mark.index()..mark.index() + txt.len(), + }); + } + scope.pop(); + }, + + _ => panic!(), + } + }, + x => todo!("{:?}", x), + } + } + + return images; +} + +pub trait SubsliceOffset<T> { + fn subslice_range(&self, inner: &Self) -> Option<std::ops::Range<usize>>; +} + +impl<T> SubsliceOffset<T> for [T] { + fn subslice_range(&self, subslice: &[T]) -> Option<std::ops::Range<usize>> { + if size_of::<T>() == 0 { + panic!("elements are zero-sized"); + } + + let self_start = self.as_ptr().addr(); + let subslice_start = subslice.as_ptr().addr(); + + let byte_start = subslice_start.wrapping_sub(self_start); + + if !byte_start.is_multiple_of(size_of::<T>()) { + return None; + } + + let start = byte_start / size_of::<T>(); + let end = start.wrapping_add(subslice.len()); + + if start <= self.len() && end <= self.len() { Some(start..end) } else { None } + } +} + +#[derive(Debug, Clone)] +struct DockerRef { + full_range: Range<usize>, + + registry: Option<Range<usize>>, + image: Range<usize>, + tag: Option<Range<usize>>, + digest: Option<Range<usize>>, +} + +impl DockerRef{ + fn parse(file: &str, chunk: &Chunk) -> DockerRef { + let mut string_range = chunk.position.clone(); + + let mut digest = None; + if let Some(idx) = file[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) = file[string_range.clone()].rfind(":") { + tag = Some(string_range.start+idx+1..string_range.end); + string_range.end = idx; + } + + let mut registry = None; + let image; + if let Some(idx) = file[string_range.clone()].find("/") { + let head = &file[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 { + full_range: chunk.position.clone(), + + registry, + image, + tag, + digest, + }; + } +} + +#[derive(Debug)] +struct Update { + position: Range<usize>, + content: String, +} + +fn fetch_new_image(file: &str, auth: &Auth, img: DockerRef, edits: &mut Vec<Update>) { + let mut resulting_ref = img.clone(); + + dbg!(&img); + let registry = img.registry.map(|x| &file[x]).unwrap_or("https://registry.jnsn.dev/"); + let tag = img.tag.map(|x| &file[x]).unwrap_or("latest"); + + if tag == "latest" || img.digest.is_some() { + let url = format!("https://{}/v2/{}/manifests/{}", registry, &file[img.image], tag); + dbg!(&url); + let mut response = auth.apply(ureq::get(url)) + .call().unwrap(); + + + let body: tinyjson::JsonValue = response + .body_mut() + .read_to_string().unwrap().parse().unwrap(); + + + let media_type : &String = body["mediaType"].get().unwrap(); + assert!(media_type == "application/vnd.docker.distribution.manifest.v2+json"); + + let digest = &response.headers()["docker-content-digest"]; + // resulting_ref.digest = Some(digest.to_str().unwrap().to_string()); + + // dbg!(&resulting_ref.raw[resulting_ref.digest.as_ref().unwrap().clone()]); + dbg!(&resulting_ref); + + edits.push(Update{ + position: img.full_range, + content: "AHH".to_string(), + }); + } +} + +fn main() { + let argv: Vec<String> = std::env::args().collect(); + let mut it = argv.iter(); + let cmd = &it.next().unwrap(); + + let mut auth = Auth::new(); + let mut positional: Vec<&str> = vec!(); + + loop { + match it.next().map(|x| x.as_str()) { + None => break, + Some("--auth") => { + if let Some(username) = it.next() && let Some(password) = it.next() { + auth.parse(username, password); + } else { + println!("Error: --auth requires two parameters"); + help(cmd); + std::process::exit(1); + } + }, + Some("-h") | Some("--help") => { + help(cmd); + std::process::exit(0); + }, + Some(arg) => positional.push(arg), + }; + } + + if positional.len() != 1 { + panic!("Bad arguments"); + } + let file = positional[0]; + + let file_content = &std::fs::read_to_string(file).unwrap(); + let yaml = Parser::new_from_str(&file_content); + let images : Vec<_> = scan_yaml_for_images(yaml); + + let mut edits = vec![]; + + let images : Vec<_> = images.iter() + .map(|x| DockerRef::parse(&file_content, x)) + .map(|x| fetch_new_image(&file_content, &auth, x, &mut edits)) + .collect(); + + dbg!(&images); + dbg!(&edits); + // dbg!(&file_content[images[0].digest.as_ref().unwrap().clone()]); + + let body: String = auth.apply(ureq::get("https://registry.jnsn.dev/v2/autobrr/tags/list")) + .call().unwrap() + .body_mut() + .read_to_string().unwrap(); + // dbg!(body); +} |
