mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-14 16:13:48 -05:00
Improve logging
This commit is contained in:
@@ -1 +0,0 @@
|
||||
rust 1.79.0
|
||||
63
src/check.rs
63
src/check.rs
@@ -1,12 +1,14 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use json::JsonValue;
|
||||
use chrono::Local;
|
||||
|
||||
use crate::{
|
||||
debug,
|
||||
docker::get_images_from_docker_daemon,
|
||||
image::Image,
|
||||
info,
|
||||
registry::{check_auth, get_latest_digests, get_token},
|
||||
utils::{new_reqwest_client, unsplit_image},
|
||||
utils::{new_reqwest_client, unsplit_image, CliConfig},
|
||||
};
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
@@ -29,44 +31,48 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_all_updates(
|
||||
socket: Option<String>,
|
||||
config: &JsonValue,
|
||||
) -> Vec<(String, Option<bool>)> {
|
||||
let local_images = get_images_from_docker_daemon(socket).await;
|
||||
pub async fn get_all_updates(options: &CliConfig) -> Vec<(String, Option<bool>)> {
|
||||
let start = Local::now().timestamp_millis();
|
||||
let local_images = get_images_from_docker_daemon(options).await;
|
||||
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);
|
||||
image_map.insert(img, image.digest.clone());
|
||||
};
|
||||
let mut registries: Vec<&String> = local_images
|
||||
.iter()
|
||||
.map(|image| &image.registry)
|
||||
.collect();
|
||||
}
|
||||
let mut registries: Vec<&String> = local_images.iter().map(|image| &image.registry).collect();
|
||||
registries.unique();
|
||||
let mut remote_images: Vec<Image> = Vec::with_capacity(local_images.len());
|
||||
let client = new_reqwest_client();
|
||||
for registry in registries {
|
||||
if options.verbose {
|
||||
debug!("Checking images from registry {}", registry)
|
||||
}
|
||||
let images: Vec<&Image> = local_images
|
||||
.iter()
|
||||
.filter(|image| &image.registry == registry)
|
||||
.collect();
|
||||
let credentials = config["authentication"][registry]
|
||||
let credentials = options.config["authentication"][registry]
|
||||
.clone()
|
||||
.take_string()
|
||||
.or(None);
|
||||
let mut latest_images = match check_auth(registry, config, &client).await {
|
||||
let mut latest_images = match check_auth(registry, options, &client).await {
|
||||
Some(auth_url) => {
|
||||
let token = get_token(images.clone(), &auth_url, &credentials, &client).await;
|
||||
get_latest_digests(images, Some(&token), config, &client).await
|
||||
if options.verbose {
|
||||
debug!("Using token {}", token);
|
||||
}
|
||||
None => get_latest_digests(images, None, config, &client).await,
|
||||
get_latest_digests(images, Some(&token), options, &client).await
|
||||
}
|
||||
None => get_latest_digests(images, None, options, &client).await,
|
||||
};
|
||||
remote_images.append(&mut latest_images);
|
||||
}
|
||||
if options.verbose {
|
||||
debug!("Collecting results")
|
||||
}
|
||||
let mut result: Vec<(String, Option<bool>)> = Vec::new();
|
||||
remote_images.iter().for_each(|image| {
|
||||
let img = unsplit_image(&image.registry, &image.repository, &image.tag);
|
||||
let img = unsplit_image(image);
|
||||
match &image.digest {
|
||||
Some(d) => {
|
||||
let r = d != image_map.get(&img).unwrap().as_ref().unwrap();
|
||||
@@ -75,24 +81,33 @@ pub async fn get_all_updates(
|
||||
None => result.push((img, None)),
|
||||
}
|
||||
});
|
||||
let end = Local::now().timestamp_millis();
|
||||
info!(
|
||||
"✨ Checked {} images in {}ms",
|
||||
local_images.len(),
|
||||
end - start
|
||||
);
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub async fn get_update(image: &str, socket: Option<String>, config: &JsonValue) -> Option<bool> {
|
||||
let local_image = get_image_from_docker_daemon(socket, image).await;
|
||||
let credentials = config["authentication"][&local_image.registry]
|
||||
pub async fn get_update(image: &str, options: &CliConfig) -> Option<bool> {
|
||||
let local_image = get_image_from_docker_daemon(options.socket.clone(), image).await;
|
||||
let credentials = options.config["authentication"][&local_image.registry]
|
||||
.clone()
|
||||
.take_string()
|
||||
.or(None);
|
||||
let client = new_reqwest_client();
|
||||
let token = match check_auth(&local_image.registry, config, &client).await {
|
||||
let token = match check_auth(&local_image.registry, options, &client).await {
|
||||
Some(auth_url) => get_token(vec![&local_image], &auth_url, &credentials, &client).await,
|
||||
None => String::new(),
|
||||
};
|
||||
if options.verbose {
|
||||
debug!("Using token {}", token);
|
||||
};
|
||||
let remote_image = match token.as_str() {
|
||||
"" => get_latest_digest(&local_image, None, config, &client).await,
|
||||
_ => get_latest_digest(&local_image, Some(&token), config, &client).await,
|
||||
"" => get_latest_digest(&local_image, None, options, &client).await,
|
||||
_ => get_latest_digest(&local_image, Some(&token), options, &client).await,
|
||||
};
|
||||
match &remote_image.digest {
|
||||
Some(d) => Some(d != &local_image.digest.unwrap()),
|
||||
|
||||
@@ -4,7 +4,11 @@ use bollard::{secret::ImageSummary, ClientVersion, Docker};
|
||||
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, CliConfig},
|
||||
};
|
||||
|
||||
fn create_docker_client(socket: Option<String>) -> Docker {
|
||||
let client: Result<Docker, bollard::errors::Error> = match socket {
|
||||
@@ -25,8 +29,8 @@ fn create_docker_client(socket: Option<String>) -> Docker {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_images_from_docker_daemon(socket: Option<String>) -> Vec<Image> {
|
||||
let client: Docker = create_docker_client(socket);
|
||||
pub async fn get_images_from_docker_daemon(options: &CliConfig) -> Vec<Image> {
|
||||
let client: Docker = create_docker_client(options.socket.clone());
|
||||
let images: Vec<ImageSummary> = match client.list_images::<String>(None).await {
|
||||
Ok(images) => images,
|
||||
Err(e) => {
|
||||
@@ -35,14 +39,14 @@ pub async fn get_images_from_docker_daemon(socket: Option<String>) -> Vec<Image>
|
||||
};
|
||||
let mut handles = Vec::new();
|
||||
for image in images {
|
||||
handles.push(Image::from(image))
|
||||
};
|
||||
join_all(handles).await.iter().filter(|img| {
|
||||
match img {
|
||||
Some(_) => true,
|
||||
None => false
|
||||
handles.push(Image::from(image, options))
|
||||
}
|
||||
}).map(|img| img.clone().unwrap()).collect()
|
||||
join_all(handles)
|
||||
.await
|
||||
.iter()
|
||||
.filter(|img| img.is_some())
|
||||
.map(|img| img.clone().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[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) {
|
||||
let sorted_updates = sort_update_vec(updates);
|
||||
let term_width: usize = termsize::get()
|
||||
.unwrap_or_else(|| termsize::Size { rows: 24, cols: 80 })
|
||||
.unwrap_or(termsize::Size { rows: 24, cols: 80 })
|
||||
.cols as usize;
|
||||
for update in sorted_updates {
|
||||
let description = match update.1 {
|
||||
|
||||
18
src/image.rs
18
src/image.rs
@@ -1,6 +1,9 @@
|
||||
use bollard::secret::ImageSummary;
|
||||
|
||||
use crate::utils::split_image;
|
||||
use crate::{
|
||||
debug,
|
||||
utils::{split_image, CliConfig},
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Image {
|
||||
@@ -11,10 +14,9 @@ pub struct Image {
|
||||
}
|
||||
|
||||
impl Image {
|
||||
pub async fn from(image: ImageSummary) -> Option<Self> {
|
||||
pub async fn from(image: ImageSummary, options: &CliConfig) -> 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 (registry, repository, tag) = split_image(&image.repo_tags[0]);
|
||||
let image = Image {
|
||||
registry,
|
||||
repository,
|
||||
@@ -27,8 +29,12 @@ impl Image {
|
||||
.to_string(),
|
||||
),
|
||||
};
|
||||
return Some(image)
|
||||
}
|
||||
return Some(image);
|
||||
} else if options.verbose {
|
||||
debug!(
|
||||
"Skipped an image\nTags: {:#?}\nDigests: {:#?}",
|
||||
image.repo_tags, image.repo_digests
|
||||
)
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
33
src/main.rs
33
src/main.rs
@@ -6,7 +6,7 @@ use formatting::{print_raw_update, print_raw_updates, print_update, print_update
|
||||
#[cfg(feature = "server")]
|
||||
use server::serve;
|
||||
use std::path::PathBuf;
|
||||
use utils::load_config;
|
||||
use utils::{load_config, CliConfig};
|
||||
|
||||
pub mod check;
|
||||
pub mod docker;
|
||||
@@ -25,6 +25,13 @@ struct Cli {
|
||||
socket: Option<String>,
|
||||
#[arg(short, long, default_value_t = String::new(), help = "Config file path")]
|
||||
config_path: String,
|
||||
#[arg(
|
||||
short,
|
||||
long,
|
||||
default_value_t = false,
|
||||
help = "Enable verbose (debug) logging"
|
||||
)]
|
||||
verbose: bool,
|
||||
#[command(subcommand)]
|
||||
command: Option<Commands>,
|
||||
}
|
||||
@@ -64,23 +71,35 @@ async fn main() {
|
||||
"" => None,
|
||||
path => Some(PathBuf::from(path)),
|
||||
};
|
||||
let config = load_config(cfg_path);
|
||||
if cli.verbose {
|
||||
debug!("CLI options:");
|
||||
debug!("Config path: {:?}", cfg_path);
|
||||
debug!("Socket: {:?}", &cli.socket)
|
||||
}
|
||||
let cli_config = CliConfig {
|
||||
socket: cli.socket,
|
||||
verbose: cli.verbose,
|
||||
config: load_config(cfg_path),
|
||||
};
|
||||
if cli.verbose {
|
||||
debug!("Config: {}", cli_config.config)
|
||||
}
|
||||
match &cli.command {
|
||||
#[cfg(feature = "cli")]
|
||||
Some(Commands::Check { image, icons, raw }) => match image {
|
||||
Some(name) => {
|
||||
let has_update = get_update(name, cli.socket, &config).await;
|
||||
let has_update = get_update(name, &cli_config).await;
|
||||
match raw {
|
||||
true => print_raw_update(name, &has_update),
|
||||
false => print_update(name, &has_update),
|
||||
};
|
||||
}
|
||||
None => {
|
||||
match raw {
|
||||
true => print_raw_updates(&get_all_updates(cli.socket, &config).await),
|
||||
match *raw || cli.verbose {
|
||||
true => print_raw_updates(&get_all_updates(&cli_config).await),
|
||||
false => {
|
||||
let spinner = Spinner::new();
|
||||
let updates = get_all_updates(cli.socket, &config).await;
|
||||
let updates = get_all_updates(&cli_config).await;
|
||||
spinner.succeed();
|
||||
print_updates(&updates, icons);
|
||||
}
|
||||
@@ -89,7 +108,7 @@ async fn main() {
|
||||
},
|
||||
#[cfg(feature = "server")]
|
||||
Some(Commands::Serve { port }) => {
|
||||
let _ = serve(port, cli.socket, config).await;
|
||||
let _ = serve(port, &cli_config).await;
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
|
||||
@@ -4,15 +4,28 @@ use json::JsonValue;
|
||||
use http_auth::parse_challenges;
|
||||
use reqwest_middleware::ClientWithMiddleware;
|
||||
|
||||
use crate::{error, image::Image, warn};
|
||||
use crate::{debug, error, image::Image, utils::CliConfig, warn};
|
||||
|
||||
pub async fn check_auth(registry: &str, config: &JsonValue, client: &ClientWithMiddleware) -> Option<String> {
|
||||
let protocol = if config["insecure_registries"].contains(registry) {
|
||||
pub async fn check_auth(
|
||||
registry: &str,
|
||||
options: &CliConfig,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Option<String> {
|
||||
let protocol = if options.config["insecure_registries"].contains(registry) {
|
||||
if options.verbose {
|
||||
debug!(
|
||||
"{} is configured as an insecure registry. Downgrading to HTTP",
|
||||
registry
|
||||
);
|
||||
};
|
||||
"http"
|
||||
} else {
|
||||
"https"
|
||||
};
|
||||
let response = client.get(&format!("{}://{}/v2/", protocol, registry)).send().await;
|
||||
let response = client
|
||||
.get(format!("{}://{}/v2/", protocol, registry))
|
||||
.send()
|
||||
.await;
|
||||
match response {
|
||||
Ok(r) => {
|
||||
let status = r.status().as_u16();
|
||||
@@ -27,10 +40,14 @@ pub async fn check_auth(registry: &str, config: &JsonValue, client: &ClientWithM
|
||||
} else if status == 200 {
|
||||
None
|
||||
} else {
|
||||
warn!("Received unexpected status code {}\nResponse: {}", status, r.text().await.unwrap());
|
||||
warn!(
|
||||
"Received unexpected status code {}\nResponse: {}",
|
||||
status,
|
||||
r.text().await.unwrap()
|
||||
);
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(e) => {
|
||||
if e.is_connect() {
|
||||
warn!("Connection to registry {} failed.", ®istry);
|
||||
@@ -42,14 +59,20 @@ pub async fn check_auth(registry: &str, config: &JsonValue, client: &ClientWithM
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_latest_digest(image: &Image, token: Option<&String>, config: &JsonValue, client: &ClientWithMiddleware) -> Image {
|
||||
let protocol =
|
||||
if config["insecure_registries"].contains(json::JsonValue::from(image.registry.clone())) {
|
||||
pub async fn get_latest_digest(
|
||||
image: &Image,
|
||||
token: Option<&String>,
|
||||
options: &CliConfig,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Image {
|
||||
let protocol = if options.config["insecure_registries"]
|
||||
.contains(json::JsonValue::from(image.registry.clone()))
|
||||
{
|
||||
"http"
|
||||
} else {
|
||||
"https"
|
||||
};
|
||||
let mut request = client.head(&format!(
|
||||
let mut request = client.head(format!(
|
||||
"{}://{}/v2/{}/manifests/{}",
|
||||
protocol, &image.registry, &image.repository, &image.tag
|
||||
));
|
||||
@@ -90,30 +113,39 @@ pub async fn get_latest_digest(image: &Image, token: Option<&String>, config: &J
|
||||
digest: Some(digest.to_str().unwrap().to_string()),
|
||||
..image.clone()
|
||||
},
|
||||
None => error!("Server returned invalid response! No docker-content-digest!\n{:#?}", raw_response),
|
||||
None => error!(
|
||||
"Server returned invalid response! No docker-content-digest!\n{:#?}",
|
||||
raw_response
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_latest_digests(
|
||||
images: Vec<&Image>,
|
||||
token: Option<&String>,
|
||||
config: &JsonValue,
|
||||
client: &ClientWithMiddleware
|
||||
options: &CliConfig,
|
||||
client: &ClientWithMiddleware,
|
||||
) -> Vec<Image> {
|
||||
let mut handles = Vec::new();
|
||||
for image in images {
|
||||
handles.push(get_latest_digest(image, token, config, client))
|
||||
handles.push(get_latest_digest(image, token, options, client))
|
||||
}
|
||||
join_all(handles).await
|
||||
}
|
||||
|
||||
pub async fn get_token(images: Vec<&Image>, auth_url: &str, credentials: &Option<String>, client: &ClientWithMiddleware) -> 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();
|
||||
for image in &images {
|
||||
final_url = format!("{}&scope=repository:{}:pull", final_url, image.repository);
|
||||
}
|
||||
let mut base_request =
|
||||
client.get(&final_url).header("Accept", "application/vnd.oci.image.index.v1+json"); // Seems to be unnecessary. Will probably remove in the future
|
||||
let mut base_request = 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 {
|
||||
Some(creds) => base_request.header("Authorization", &format!("Basic {}", creds)),
|
||||
None => base_request,
|
||||
|
||||
@@ -15,8 +15,8 @@ use xitca_web::{
|
||||
|
||||
use crate::{
|
||||
check::get_all_updates,
|
||||
error,
|
||||
utils::{sort_update_vec, to_json},
|
||||
error, info,
|
||||
utils::{sort_update_vec, to_json, CliConfig},
|
||||
};
|
||||
|
||||
const HTML: &str = include_str!("static/index.html");
|
||||
@@ -26,9 +26,10 @@ const FAVICON_ICO: &[u8] = include_bytes!("static/favicon.ico");
|
||||
const FAVICON_SVG: &[u8] = include_bytes!("static/favicon.svg");
|
||||
const APPLE_TOUCH_ICON: &[u8] = include_bytes!("static/apple-touch-icon.png");
|
||||
|
||||
pub async fn serve(port: &u16, socket: Option<String>, config: JsonValue) -> std::io::Result<()> {
|
||||
println!("Starting server, please wait...");
|
||||
let data = ServerData::new(socket, config).await;
|
||||
pub async fn serve(port: &u16, options: &CliConfig) -> std::io::Result<()> {
|
||||
info!("Starting server, please wait...");
|
||||
let data = ServerData::new(options).await;
|
||||
info!("Ready to start!");
|
||||
App::new()
|
||||
.with_state(Arc::new(Mutex::new(data)))
|
||||
.at("/", get(handler_service(_static)))
|
||||
@@ -93,31 +94,28 @@ struct ServerData {
|
||||
template: String,
|
||||
raw_updates: Vec<(String, Option<bool>)>,
|
||||
json: JsonValue,
|
||||
socket: Option<String>,
|
||||
config: JsonValue,
|
||||
options: CliConfig,
|
||||
theme: &'static str,
|
||||
}
|
||||
|
||||
impl ServerData {
|
||||
async fn new(socket: Option<String>, config: JsonValue) -> Self {
|
||||
async fn new(options: &CliConfig) -> Self {
|
||||
let mut s = Self {
|
||||
socket,
|
||||
options: options.clone(),
|
||||
template: String::new(),
|
||||
json: json::object! {
|
||||
metrics: json::object! {},
|
||||
images: json::object! {},
|
||||
},
|
||||
raw_updates: Vec::new(),
|
||||
config,
|
||||
theme: "neutral",
|
||||
};
|
||||
s.refresh().await;
|
||||
s
|
||||
}
|
||||
async fn refresh(&mut self) {
|
||||
let updates = sort_update_vec(
|
||||
&get_all_updates(self.socket.clone(), &self.config["authentication"]).await,
|
||||
);
|
||||
info!("Refreshing data");
|
||||
let updates = sort_update_vec(&get_all_updates(&self.options).await);
|
||||
self.raw_updates = updates;
|
||||
let template = liquid::ParserBuilder::with_stdlib()
|
||||
.build()
|
||||
@@ -134,8 +132,11 @@ impl ServerData {
|
||||
.collect::<Vec<Object>>();
|
||||
self.json = to_json(&self.raw_updates);
|
||||
let last_updated = Local::now();
|
||||
self.json["last_updated"] = last_updated.to_rfc3339_opts(chrono::SecondsFormat::Secs, true).to_string().into();
|
||||
self.theme = match &self.config["theme"].as_str() {
|
||||
self.json["last_updated"] = last_updated
|
||||
.to_rfc3339_opts(chrono::SecondsFormat::Secs, true)
|
||||
.to_string()
|
||||
.into();
|
||||
self.theme = match &self.options.config["theme"].as_str() {
|
||||
Some(t) => match *t {
|
||||
"default" => "neutral",
|
||||
"blue" => "gray",
|
||||
|
||||
84
src/utils.rs
84
src/utils.rs
@@ -1,27 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{error, image::Image};
|
||||
use json::{object, JsonValue};
|
||||
use once_cell::sync::Lazy;
|
||||
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)
|
||||
#[macro_export]
|
||||
macro_rules! error {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!($($arg)*);
|
||||
std::process::exit(1);
|
||||
})
|
||||
}
|
||||
|
||||
// A small macro to print in yellow as a warning
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[93m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||
|
||||
static RE: Lazy<Regex> = Lazy::new(|| {
|
||||
Regex::new(
|
||||
@@ -62,22 +46,22 @@ pub fn split_image(image: &str) -> (String, String, String) {
|
||||
}
|
||||
|
||||
/// Given an image's parts which were previously created by split_image, recreate a reference that docker would use. This means removing the registry part, if it's Docker Hub and removing "library" if the image is official
|
||||
pub fn unsplit_image(registry: &str, repository: &str, tag: &str) -> String {
|
||||
let reg = match registry {
|
||||
pub fn unsplit_image(image: &Image) -> String {
|
||||
let reg = match image.registry.as_str() {
|
||||
"registry-1.docker.io" => String::new(),
|
||||
r => format!("{}/", r),
|
||||
};
|
||||
let repo = match repository.split('/').collect::<Vec<&str>>()[0] {
|
||||
let repo = match image.repository.split('/').collect::<Vec<&str>>()[0] {
|
||||
"library" => {
|
||||
if reg.is_empty() {
|
||||
repository.strip_prefix("library/").unwrap()
|
||||
image.repository.strip_prefix("library/").unwrap()
|
||||
} else {
|
||||
repository
|
||||
image.repository.as_str()
|
||||
}
|
||||
}
|
||||
_ => repository,
|
||||
_ => image.repository.as_str(),
|
||||
};
|
||||
format!("{}{}:{}", reg, repo, tag)
|
||||
format!("{}{}:{}", reg, repo, image.tag)
|
||||
}
|
||||
|
||||
/// Sorts the update vector alphabetically and where Some(true) > Some(false) > None
|
||||
@@ -132,10 +116,7 @@ pub fn to_json(updates: &[(String, Option<bool>)]) -> JsonValue {
|
||||
.iter()
|
||||
.filter(|&(_, value)| *value == Some(true))
|
||||
.count();
|
||||
let unknown = updates
|
||||
.iter()
|
||||
.filter(|&(_, value)| value.is_none())
|
||||
.count();
|
||||
let unknown = updates.iter().filter(|&(_, value)| value.is_none()).count();
|
||||
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("update_available", update_available);
|
||||
@@ -143,8 +124,51 @@ pub fn to_json(updates: &[(String, Option<bool>)]) -> JsonValue {
|
||||
json_data
|
||||
}
|
||||
|
||||
/// Struct to hold some config values to avoid having to pass them all the time
|
||||
#[derive(Clone)]
|
||||
pub struct CliConfig {
|
||||
pub socket: Option<String>,
|
||||
pub verbose: bool,
|
||||
pub config: JsonValue,
|
||||
}
|
||||
|
||||
// Logging
|
||||
|
||||
/// 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_rules! error {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[41m ERROR \x1b[0m {}", format!($($arg)*));
|
||||
std::process::exit(1);
|
||||
})
|
||||
}
|
||||
|
||||
// A small macro to print in yellow as a warning
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[103m WARN \x1b[0m {}", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! info {
|
||||
($($arg:tt)*) => ({
|
||||
println!("\x1b[44m INFO \x1b[0m {}", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! debug {
|
||||
($($arg:tt)*) => ({
|
||||
println!("\x1b[48:5:57m DEBUG \x1b[0m {}", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_reqwest_client() -> ClientWithMiddleware {
|
||||
ClientBuilder::new(reqwest::Client::new())
|
||||
.with(RetryTransientMiddleware::new_with_policy(ExponentialBackoff::builder().build_with_max_retries(3)))
|
||||
.with(RetryTransientMiddleware::new_with_policy(
|
||||
ExponentialBackoff::builder().build_with_max_retries(3),
|
||||
))
|
||||
.build()
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
nodejs 21.6.2
|
||||
nodejs 22.8.0
|
||||
|
||||
Reference in New Issue
Block a user