mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-15 00:23:48 -05:00
Removed all threading and switched everything to async. >2x speedup 🚀
This commit is contained in:
777
Cargo.lock
generated
777
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -6,18 +6,20 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
clap = { version = "4.5.7", features = ["derive"] }
|
clap = { version = "4.5.7", features = ["derive"] }
|
||||||
indicatif = { version = "0.17.8", optional = true }
|
indicatif = { version = "0.17.8", optional = true }
|
||||||
tokio = {version = "1.38.0", features = ["rt", "rt-multi-thread", "macros"]}
|
tokio = {version = "1.38.0", features = ["macros"]}
|
||||||
ureq = { version = "2.9.7", features = ["tls"] }
|
|
||||||
rayon = "1.10.0"
|
|
||||||
xitca-web = { version = "0.5.0", optional = true, features = ["logger"] }
|
xitca-web = { version = "0.5.0", optional = true, features = ["logger"] }
|
||||||
liquid = { version = "0.26.6", optional = true }
|
liquid = { version = "0.26.6", optional = true }
|
||||||
bollard = "0.16.1"
|
bollard = "0.16.1"
|
||||||
once_cell = "1.19.0"
|
once_cell = "1.19.0"
|
||||||
http-auth = { version = "0.1.9", features = [] }
|
http-auth = { version = "0.1.9", default-features = false, features = [] }
|
||||||
termsize = { version = "0.1.8", optional = true }
|
termsize = { version = "0.1.8", optional = true }
|
||||||
regex = "1.10.5"
|
regex = "1.10.5"
|
||||||
chrono = { version = "0.4.38", default-features = false, features = ["std", "alloc", "clock"], optional = true }
|
chrono = { version = "0.4.38", default-features = false, features = ["std", "alloc", "clock"], optional = true }
|
||||||
json = "0.12.4"
|
json = "0.12.4"
|
||||||
|
reqwest = "0.12.7"
|
||||||
|
futures = "0.3.30"
|
||||||
|
reqwest-retry = "0.6.1"
|
||||||
|
reqwest-middleware = "0.3.3"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["server", "cli"]
|
default = ["server", "cli"]
|
||||||
|
|||||||
50
src/check.rs
50
src/check.rs
@@ -1,16 +1,12 @@
|
|||||||
use std::{
|
use std::collections::{HashMap, HashSet};
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
sync::Mutex,
|
|
||||||
};
|
|
||||||
|
|
||||||
use json::JsonValue;
|
use json::JsonValue;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
docker::get_images_from_docker_daemon,
|
docker::get_images_from_docker_daemon,
|
||||||
image::Image,
|
image::Image,
|
||||||
registry::{check_auth, get_latest_digests, get_token},
|
registry::{check_auth, get_latest_digests, get_token},
|
||||||
utils::unsplit_image,
|
utils::{new_reqwest_client, unsplit_image},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
@@ -37,49 +33,48 @@ pub async fn get_all_updates(
|
|||||||
socket: Option<String>,
|
socket: Option<String>,
|
||||||
config: &JsonValue,
|
config: &JsonValue,
|
||||||
) -> Vec<(String, Option<bool>)> {
|
) -> Vec<(String, Option<bool>)> {
|
||||||
let image_map_mutex: Mutex<HashMap<String, &Option<String>>> = Mutex::new(HashMap::new());
|
|
||||||
let local_images = get_images_from_docker_daemon(socket).await;
|
let local_images = get_images_from_docker_daemon(socket).await;
|
||||||
local_images.par_iter().for_each(|image| {
|
let mut image_map: HashMap<String, Option<String>> = HashMap::with_capacity(local_images.len());
|
||||||
|
for image in &local_images {
|
||||||
let img = unsplit_image(&image.registry, &image.repository, &image.tag);
|
let img = unsplit_image(&image.registry, &image.repository, &image.tag);
|
||||||
image_map_mutex.lock().unwrap().insert(img, &image.digest);
|
image_map.insert(img, image.digest.clone());
|
||||||
});
|
};
|
||||||
let image_map = image_map_mutex.lock().unwrap().clone();
|
|
||||||
let mut registries: Vec<&String> = local_images
|
let mut registries: Vec<&String> = local_images
|
||||||
.par_iter()
|
.iter()
|
||||||
.map(|image| &image.registry)
|
.map(|image| &image.registry)
|
||||||
.collect();
|
.collect();
|
||||||
registries.unique();
|
registries.unique();
|
||||||
let mut remote_images: Vec<Image> = Vec::new();
|
let mut remote_images: Vec<Image> = Vec::with_capacity(local_images.len());
|
||||||
|
let client = new_reqwest_client();
|
||||||
for registry in registries {
|
for registry in registries {
|
||||||
let images: Vec<&Image> = local_images
|
let images: Vec<&Image> = local_images
|
||||||
.par_iter()
|
.iter()
|
||||||
.filter(|image| &image.registry == registry)
|
.filter(|image| &image.registry == registry)
|
||||||
.collect();
|
.collect();
|
||||||
let credentials = config["authentication"][registry]
|
let credentials = config["authentication"][registry]
|
||||||
.clone()
|
.clone()
|
||||||
.take_string()
|
.take_string()
|
||||||
.or(None);
|
.or(None);
|
||||||
let mut latest_images = match check_auth(registry, config) {
|
let mut latest_images = match check_auth(registry, config, &client).await {
|
||||||
Some(auth_url) => {
|
Some(auth_url) => {
|
||||||
let token = get_token(images.clone(), &auth_url, &credentials);
|
let token = get_token(images.clone(), &auth_url, &credentials, &client).await;
|
||||||
get_latest_digests(images, Some(&token), config)
|
get_latest_digests(images, Some(&token), config, &client).await
|
||||||
}
|
}
|
||||||
None => get_latest_digests(images, None, config),
|
None => get_latest_digests(images, None, config, &client).await,
|
||||||
};
|
};
|
||||||
remote_images.append(&mut latest_images);
|
remote_images.append(&mut latest_images);
|
||||||
}
|
}
|
||||||
let result_mutex: Mutex<Vec<(String, Option<bool>)>> = Mutex::new(Vec::new());
|
let mut result: Vec<(String, Option<bool>)> = Vec::new();
|
||||||
remote_images.par_iter().for_each(|image| {
|
remote_images.iter().for_each(|image| {
|
||||||
let img = unsplit_image(&image.registry, &image.repository, &image.tag);
|
let img = unsplit_image(&image.registry, &image.repository, &image.tag);
|
||||||
match &image.digest {
|
match &image.digest {
|
||||||
Some(d) => {
|
Some(d) => {
|
||||||
let r = d != image_map.get(&img).unwrap().as_ref().unwrap();
|
let r = d != image_map.get(&img).unwrap().as_ref().unwrap();
|
||||||
result_mutex.lock().unwrap().push((img, Some(r)))
|
result.push((img, Some(r)))
|
||||||
}
|
}
|
||||||
None => result_mutex.lock().unwrap().push((img, None)),
|
None => result.push((img, None)),
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let result = result_mutex.lock().unwrap().clone();
|
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,13 +85,14 @@ pub async fn get_update(image: &str, socket: Option<String>, config: &JsonValue)
|
|||||||
.clone()
|
.clone()
|
||||||
.take_string()
|
.take_string()
|
||||||
.or(None);
|
.or(None);
|
||||||
let token = match check_auth(&local_image.registry, config) {
|
let client = new_reqwest_client();
|
||||||
Some(auth_url) => get_token(vec![&local_image], &auth_url, &credentials),
|
let token = match check_auth(&local_image.registry, config, &client).await {
|
||||||
|
Some(auth_url) => get_token(vec![&local_image], &auth_url, &credentials, &client).await,
|
||||||
None => String::new(),
|
None => String::new(),
|
||||||
};
|
};
|
||||||
let remote_image = match token.as_str() {
|
let remote_image = match token.as_str() {
|
||||||
"" => get_latest_digest(&local_image, None, config),
|
"" => get_latest_digest(&local_image, None, config, &client).await,
|
||||||
_ => get_latest_digest(&local_image, Some(&token), config),
|
_ => get_latest_digest(&local_image, Some(&token), config, &client).await,
|
||||||
};
|
};
|
||||||
match &remote_image.digest {
|
match &remote_image.digest {
|
||||||
Some(d) => Some(d != &local_image.digest.unwrap()),
|
Some(d) => Some(d != &local_image.digest.unwrap()),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ use bollard::{secret::ImageSummary, ClientVersion, Docker};
|
|||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
use bollard::secret::ImageInspect;
|
use bollard::secret::ImageInspect;
|
||||||
|
use futures::future::join_all;
|
||||||
|
|
||||||
use crate::{error, image::Image, utils::split_image};
|
use crate::{error, image::Image, utils::split_image};
|
||||||
|
|
||||||
@@ -32,27 +33,16 @@ pub async fn get_images_from_docker_daemon(socket: Option<String>) -> Vec<Image>
|
|||||||
error!("Failed to retrieve list of images available!\n{}", e)
|
error!("Failed to retrieve list of images available!\n{}", e)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let mut result: Vec<Image> = Vec::new();
|
let mut handles = Vec::new();
|
||||||
for image in images {
|
for image in images {
|
||||||
if !image.repo_tags.is_empty() && !image.repo_digests.is_empty() {
|
handles.push(Image::from(image))
|
||||||
for t in &image.repo_tags {
|
};
|
||||||
let (registry, repository, tag) = split_image(t);
|
join_all(handles).await.iter().filter(|img| {
|
||||||
result.push(Image {
|
match img {
|
||||||
registry,
|
Some(_) => true,
|
||||||
repository,
|
None => false
|
||||||
tag,
|
|
||||||
digest: Some(
|
|
||||||
image.repo_digests[0]
|
|
||||||
.clone()
|
|
||||||
.split('@')
|
|
||||||
.collect::<Vec<&str>>()[1]
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}).map(|img| img.clone().unwrap()).collect()
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use crate::utils::{sort_update_vec, to_json};
|
|||||||
pub fn print_updates(updates: &[(String, Option<bool>)], icons: &bool) {
|
pub fn print_updates(updates: &[(String, Option<bool>)], icons: &bool) {
|
||||||
let sorted_updates = sort_update_vec(updates);
|
let sorted_updates = sort_update_vec(updates);
|
||||||
let term_width: usize = termsize::get()
|
let term_width: usize = termsize::get()
|
||||||
.unwrap_or(termsize::Size { rows: 24, cols: 80 })
|
.unwrap_or_else(|| termsize::Size { rows: 24, cols: 80 })
|
||||||
.cols as usize;
|
.cols as usize;
|
||||||
for update in sorted_updates {
|
for update in sorted_updates {
|
||||||
let description = match update.1 {
|
let description = match update.1 {
|
||||||
|
|||||||
28
src/image.rs
28
src/image.rs
@@ -1,3 +1,7 @@
|
|||||||
|
use bollard::secret::ImageSummary;
|
||||||
|
|
||||||
|
use crate::utils::split_image;
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct Image {
|
pub struct Image {
|
||||||
pub registry: String,
|
pub registry: String,
|
||||||
@@ -5,3 +9,27 @@ pub struct Image {
|
|||||||
pub tag: String,
|
pub tag: String,
|
||||||
pub digest: Option<String>,
|
pub digest: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Image {
|
||||||
|
pub async fn from(image: ImageSummary) -> Option<Self> {
|
||||||
|
if !image.repo_tags.is_empty() && !image.repo_digests.is_empty() {
|
||||||
|
for t in &image.repo_tags {
|
||||||
|
let (registry, repository, tag) = split_image(t);
|
||||||
|
let image = Image {
|
||||||
|
registry,
|
||||||
|
repository,
|
||||||
|
tag,
|
||||||
|
digest: Some(
|
||||||
|
image.repo_digests[0]
|
||||||
|
.clone()
|
||||||
|
.split('@')
|
||||||
|
.collect::<Vec<&str>>()[1]
|
||||||
|
.to_string(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
return Some(image)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
150
src/registry.rs
150
src/registry.rs
@@ -1,160 +1,136 @@
|
|||||||
use std::sync::Mutex;
|
use futures::future::join_all;
|
||||||
|
|
||||||
use json::JsonValue;
|
use json::JsonValue;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
|
||||||
use ureq::{Error, ErrorKind};
|
|
||||||
|
|
||||||
use http_auth::parse_challenges;
|
use http_auth::parse_challenges;
|
||||||
|
use reqwest_middleware::ClientWithMiddleware;
|
||||||
|
|
||||||
use crate::{error, image::Image, warn};
|
use crate::{error, image::Image, warn};
|
||||||
|
|
||||||
pub fn check_auth(registry: &str, config: &JsonValue) -> Option<String> {
|
pub async fn check_auth(registry: &str, config: &JsonValue, client: &ClientWithMiddleware) -> Option<String> {
|
||||||
let protocol = if config["insecure_registries"].contains(registry) {
|
let protocol = if config["insecure_registries"].contains(registry) {
|
||||||
"http"
|
"http"
|
||||||
} else {
|
} else {
|
||||||
"https"
|
"https"
|
||||||
};
|
};
|
||||||
let response = ureq::get(&format!("{}://{}/v2/", protocol, registry)).call();
|
let response = client.get(&format!("{}://{}/v2/", protocol, registry)).send().await;
|
||||||
match response {
|
match response {
|
||||||
Ok(_) => None,
|
Ok(r) => {
|
||||||
Err(Error::Status(401, response)) => match response.header("www-authenticate") {
|
let status = r.status().as_u16();
|
||||||
Some(challenge) => Some(parse_www_authenticate(challenge)),
|
if status == 401 {
|
||||||
|
match r.headers().get("www-authenticate") {
|
||||||
|
Some(challenge) => Some(parse_www_authenticate(challenge.to_str().unwrap())),
|
||||||
None => error!(
|
None => error!(
|
||||||
"Unauthorized to access registry {} and no way to authenticate was provided",
|
"Unauthorized to access registry {} and no way to authenticate was provided",
|
||||||
registry
|
registry
|
||||||
),
|
),
|
||||||
|
}
|
||||||
|
} else if status == 200 {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
warn!("Received unexpected status code {}\nResponse: {}", status, r.text().await.unwrap());
|
||||||
|
None
|
||||||
|
}
|
||||||
},
|
},
|
||||||
Err(Error::Transport(error)) => {
|
Err(e) => {
|
||||||
match error.kind() {
|
if e.is_connect() {
|
||||||
ErrorKind::Dns => {
|
warn!("Connection to registry {} failed.", ®istry);
|
||||||
warn!("Failed to lookup the IP of the registry, retrying.");
|
None
|
||||||
return check_auth(registry, config);
|
} else {
|
||||||
} // If something goes really wrong, this can get stuck in a loop
|
error!("Unexpected error: {}", e.to_string())
|
||||||
ErrorKind::ConnectionFailed => {
|
|
||||||
warn!("Connection probably timed out, retrying.");
|
|
||||||
return check_auth(registry, config);
|
|
||||||
} // Same here
|
|
||||||
_ => error!("{}", error),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => error!("{}", e),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_digest(image: &Image, token: Option<&String>, config: &JsonValue) -> Image {
|
pub async fn get_latest_digest(image: &Image, token: Option<&String>, config: &JsonValue, client: &ClientWithMiddleware) -> Image {
|
||||||
let protocol =
|
let protocol =
|
||||||
if config["insecure_registries"].contains(json::JsonValue::from(image.registry.clone())) {
|
if config["insecure_registries"].contains(json::JsonValue::from(image.registry.clone())) {
|
||||||
"http"
|
"http"
|
||||||
} else {
|
} else {
|
||||||
"https"
|
"https"
|
||||||
};
|
};
|
||||||
let mut request = ureq::head(&format!(
|
let mut request = client.head(&format!(
|
||||||
"{}://{}/v2/{}/manifests/{}",
|
"{}://{}/v2/{}/manifests/{}",
|
||||||
protocol, &image.registry, &image.repository, &image.tag
|
protocol, &image.registry, &image.repository, &image.tag
|
||||||
));
|
));
|
||||||
if let Some(t) = token {
|
if let Some(t) = token {
|
||||||
request = request.set("Authorization", &format!("Bearer {}", t));
|
request = request.header("Authorization", &format!("Bearer {}", t));
|
||||||
}
|
}
|
||||||
let raw_response = match request
|
let raw_response = match request
|
||||||
.set("Accept", "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.index.v1+json")
|
.header("Accept", "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.index.v1+json")
|
||||||
.call()
|
.send().await
|
||||||
{
|
{
|
||||||
Ok(response) => response,
|
Ok(response) => {
|
||||||
Err(Error::Status(401, response)) => {
|
let status = response.status();
|
||||||
|
if status == 401 {
|
||||||
if token.is_some() {
|
if token.is_some() {
|
||||||
warn!("Failed to authenticate to registry {} with given token!\n{}", &image.registry, token.unwrap());
|
warn!("Failed to authenticate to registry {} with given token!\n{}", &image.registry, token.unwrap());
|
||||||
|
} else {
|
||||||
|
warn!("Registry requires authentication");
|
||||||
|
}
|
||||||
|
return Image { digest: None, ..image.clone() }
|
||||||
|
} else if status == 404 {
|
||||||
|
warn!("Image {:?} not found", &image);
|
||||||
return Image { digest: None, ..image.clone() }
|
return Image { digest: None, ..image.clone() }
|
||||||
} else {
|
} else {
|
||||||
return get_latest_digest(
|
response
|
||||||
image,
|
|
||||||
Some(&get_token(
|
|
||||||
vec![image],
|
|
||||||
&parse_www_authenticate(response.header("www-authenticate").unwrap()),
|
|
||||||
&None // I think?
|
|
||||||
)),
|
|
||||||
config
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(Error::Status(_, _)) => {
|
|
||||||
return Image {
|
|
||||||
digest: None,
|
|
||||||
..image.clone()
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(Error::Transport(error)) => {
|
Err(e) => {
|
||||||
match error.kind() {
|
if e.is_connect() {
|
||||||
ErrorKind::Dns => {
|
warn!("Connection to registry failed.");
|
||||||
warn!("Failed to lookup the IP of the registry, retrying.");
|
return Image { digest: None, ..image.clone() }
|
||||||
return get_latest_digest(image, token, config)
|
} else {
|
||||||
}, // If something goes really wrong, this can get stuck in a loop
|
error!("Unexpected error: {}", e.to_string())
|
||||||
ErrorKind::ConnectionFailed => {
|
|
||||||
warn!("Connection probably timed out, retrying.");
|
|
||||||
return get_latest_digest(image, token, config)
|
|
||||||
}, // Same here
|
|
||||||
_ => error!("Failed to retrieve image digest\n{}!", error)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
match raw_response.header("docker-content-digest") {
|
match raw_response.headers().get("docker-content-digest") {
|
||||||
Some(digest) => Image {
|
Some(digest) => Image {
|
||||||
digest: Some(digest.to_string()),
|
digest: Some(digest.to_str().unwrap().to_string()),
|
||||||
..image.clone()
|
..image.clone()
|
||||||
},
|
},
|
||||||
None => error!("Server returned invalid response! No docker-content-digest!"),
|
None => error!("Server returned invalid response! No docker-content-digest!\n{:#?}", raw_response),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_latest_digests(
|
pub async fn get_latest_digests(
|
||||||
images: Vec<&Image>,
|
images: Vec<&Image>,
|
||||||
token: Option<&String>,
|
token: Option<&String>,
|
||||||
config: &JsonValue,
|
config: &JsonValue,
|
||||||
|
client: &ClientWithMiddleware
|
||||||
) -> Vec<Image> {
|
) -> Vec<Image> {
|
||||||
let result: Mutex<Vec<Image>> = Mutex::new(Vec::new());
|
let mut handles = Vec::new();
|
||||||
images.par_iter().for_each(|&image| {
|
for image in images {
|
||||||
let digest = get_latest_digest(image, token, config).digest;
|
handles.push(get_latest_digest(image, token, config, client))
|
||||||
result.lock().unwrap().push(Image {
|
}
|
||||||
digest,
|
join_all(handles).await
|
||||||
..image.clone()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
let r = result.lock().unwrap().clone();
|
|
||||||
r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_token(images: Vec<&Image>, auth_url: &str, credentials: &Option<String>) -> String {
|
pub async fn get_token(images: Vec<&Image>, auth_url: &str, credentials: &Option<String>, client: &ClientWithMiddleware) -> String {
|
||||||
let mut final_url = auth_url.to_owned();
|
let mut final_url = auth_url.to_owned();
|
||||||
for image in &images {
|
for image in &images {
|
||||||
final_url = format!("{}&scope=repository:{}:pull", final_url, image.repository);
|
final_url = format!("{}&scope=repository:{}:pull", final_url, image.repository);
|
||||||
}
|
}
|
||||||
let mut base_request =
|
let mut base_request =
|
||||||
ureq::get(&final_url).set("Accept", "application/vnd.oci.image.index.v1+json"); // Seems to be unnecesarry. Will probably remove in the future
|
client.get(&final_url).header("Accept", "application/vnd.oci.image.index.v1+json"); // Seems to be unnecessary. Will probably remove in the future
|
||||||
base_request = match credentials {
|
base_request = match credentials {
|
||||||
Some(creds) => base_request.set("Authorization", &format!("Basic {}", creds)),
|
Some(creds) => base_request.header("Authorization", &format!("Basic {}", creds)),
|
||||||
None => base_request,
|
None => base_request,
|
||||||
};
|
};
|
||||||
let raw_response = match base_request.call() {
|
let raw_response = match base_request.send().await {
|
||||||
Ok(response) => match response.into_string() {
|
Ok(response) => match response.text().await {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to parse response into string!\n{}", e)
|
error!("Failed to parse response into string!\n{}", e)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(Error::Transport(error)) => {
|
|
||||||
match error.kind() {
|
|
||||||
ErrorKind::Dns => {
|
|
||||||
warn!("Failed to lookup the IP of the registry, retrying.");
|
|
||||||
return get_token(images, auth_url, credentials);
|
|
||||||
} // If something goes really wrong, this can get stuck in a loop
|
|
||||||
ErrorKind::ConnectionFailed => {
|
|
||||||
warn!("Connection probably timed out, retrying.");
|
|
||||||
return get_token(images, auth_url, credentials);
|
|
||||||
} // Same here
|
|
||||||
_ => error!("Token request failed\n{}!", error),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Token request failed!\n{}", e)
|
if e.is_connect() {
|
||||||
|
error!("Connection to registry failed.");
|
||||||
|
} else {
|
||||||
|
error!("Token request failed!\n{}", e.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let parsed_token_response: JsonValue = match json::parse(&raw_response) {
|
let parsed_token_response: JsonValue = match json::parse(&raw_response) {
|
||||||
|
|||||||
26
src/utils.rs
26
src/utils.rs
@@ -3,6 +3,8 @@ use std::path::PathBuf;
|
|||||||
use json::{object, JsonValue};
|
use json::{object, JsonValue};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||||
|
use reqwest_retry::{RetryTransientMiddleware, policies::ExponentialBackoff};
|
||||||
|
|
||||||
/// This macro is an alternative to panic. It prints the message you give it and exits the process with code 1, without printing a stack trace. Useful for when the program has to exit due to a user error or something unexpected which is unrelated to the program (e.g. a failed web request)
|
/// This macro is an alternative to panic. It prints the message you give it and exits the process with code 1, without printing a stack trace. Useful for when the program has to exit due to a user error or something unexpected which is unrelated to the program (e.g. a failed web request)
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
@@ -21,14 +23,15 @@ macro_rules! warn {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Takes an image and splits it into registry, repository and tag. For example ghcr.io/sergi0g/cup:latest becomes ['ghcr.io', 'sergi0g/cup', 'latest'].
|
static RE: Lazy<Regex> = Lazy::new(|| {
|
||||||
pub fn split_image(image: &str) -> (String, String, String) {
|
|
||||||
static RE: Lazy<Regex> = Lazy::new(|| {
|
|
||||||
Regex::new(
|
Regex::new(
|
||||||
r#"^(?P<name>(?:(?P<registry>(?:(?:localhost|[\w-]+(?:\.[\w-]+)+)(?::\d+)?)|[\w]+:\d+)/)?(?P<repository>[a-z0-9_.-]+(?:/[a-z0-9_.-]+)*))(?::(?P<tag>[\w][\w.-]{0,127}))?$"#, // From https://regex101.com/r/nmSDPA/1
|
r#"^(?P<name>(?:(?P<registry>(?:(?:localhost|[\w-]+(?:\.[\w-]+)+)(?::\d+)?)|[\w]+:\d+)/)?(?P<repository>[a-z0-9_.-]+(?:/[a-z0-9_.-]+)*))(?::(?P<tag>[\w][\w.-]{0,127}))?$"#, // From https://regex101.com/r/nmSDPA/1
|
||||||
)
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Takes an image and splits it into registry, repository and tag. For example ghcr.io/sergi0g/cup:latest becomes ['ghcr.io', 'sergi0g/cup', 'latest'].
|
||||||
|
pub fn split_image(image: &str) -> (String, String, String) {
|
||||||
match RE.captures(image) {
|
match RE.captures(image) {
|
||||||
Some(c) => {
|
Some(c) => {
|
||||||
let registry = match c.name("registry") {
|
let registry = match c.name("registry") {
|
||||||
@@ -124,21 +127,24 @@ pub fn to_json(updates: &[(String, Option<bool>)]) -> JsonValue {
|
|||||||
let up_to_date = updates
|
let up_to_date = updates
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(_, value)| *value == Some(false))
|
.filter(|&(_, value)| *value == Some(false))
|
||||||
.collect::<Vec<&(String, Option<bool>)>>()
|
.count();
|
||||||
.len();
|
|
||||||
let update_available = updates
|
let update_available = updates
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(_, value)| *value == Some(true))
|
.filter(|&(_, value)| *value == Some(true))
|
||||||
.collect::<Vec<&(String, Option<bool>)>>()
|
.count();
|
||||||
.len();
|
|
||||||
let unknown = updates
|
let unknown = updates
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|&(_, value)| value.is_none())
|
.filter(|&(_, value)| value.is_none())
|
||||||
.collect::<Vec<&(String, Option<bool>)>>()
|
.count();
|
||||||
.len();
|
|
||||||
let _ = json_data["metrics"].insert("monitored_images", updates.len());
|
let _ = json_data["metrics"].insert("monitored_images", updates.len());
|
||||||
let _ = json_data["metrics"].insert("up_to_date", up_to_date);
|
let _ = json_data["metrics"].insert("up_to_date", up_to_date);
|
||||||
let _ = json_data["metrics"].insert("update_available", update_available);
|
let _ = json_data["metrics"].insert("update_available", update_available);
|
||||||
let _ = json_data["metrics"].insert("unknown", unknown);
|
let _ = json_data["metrics"].insert("unknown", unknown);
|
||||||
json_data
|
json_data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn new_reqwest_client() -> ClientWithMiddleware {
|
||||||
|
ClientBuilder::new(reqwest::Client::new())
|
||||||
|
.with(RetryTransientMiddleware::new_with_policy(ExponentialBackoff::builder().build_with_max_retries(3)))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user