mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-17 17:43:37 -05:00
Refactor logging, create context for passing around between functions instead of config
This commit is contained in:
47
src/check.rs
47
src/check.rs
@@ -3,21 +3,19 @@ use itertools::Itertools;
|
|||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
|
||||||
debug,
|
|
||||||
docker::get_images_from_docker_daemon,
|
docker::get_images_from_docker_daemon,
|
||||||
http::Client,
|
http::Client,
|
||||||
registry::{check_auth, get_token},
|
registry::{check_auth, get_token},
|
||||||
structs::{image::Image, update::Update},
|
structs::{image::Image, update::Update},
|
||||||
utils::request::{get_response_body, parse_json},
|
utils::request::{get_response_body, parse_json},
|
||||||
warn,
|
Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Fetches image data from other Cup instances
|
/// Fetches image data from other Cup instances
|
||||||
async fn get_remote_updates(servers: &FxHashMap<String, String>, client: &Client) -> Vec<Update> {
|
async fn get_remote_updates(ctx: &Context, client: &Client) -> Vec<Update> {
|
||||||
let mut remote_images = Vec::new();
|
let mut remote_images = Vec::new();
|
||||||
|
|
||||||
let handles: Vec<_> = servers
|
let handles: Vec<_> = ctx.config.servers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(name, url)| async {
|
.map(|(name, url)| async {
|
||||||
let url = if url.starts_with("http://") || url.starts_with("https://") {
|
let url = if url.starts_with("http://") || url.starts_with("https://") {
|
||||||
@@ -28,7 +26,7 @@ async fn get_remote_updates(servers: &FxHashMap<String, String>, client: &Client
|
|||||||
match client.get(&url, vec![], false).await {
|
match client.get(&url, vec![], false).await {
|
||||||
Ok(response) => {
|
Ok(response) => {
|
||||||
if response.status() != 200 {
|
if response.status() != 200 {
|
||||||
warn!("GET {}: Failed to fetch updates from server. Server returned invalid response code: {}",url,response.status());
|
ctx.logger.warn(format!("GET {}: Failed to fetch updates from server. Server returned invalid response code: {}",url,response.status()));
|
||||||
return Vec::new();
|
return Vec::new();
|
||||||
}
|
}
|
||||||
let json = parse_json(&get_response_body(response).await);
|
let json = parse_json(&get_response_body(response).await);
|
||||||
@@ -48,7 +46,7 @@ async fn get_remote_updates(servers: &FxHashMap<String, String>, client: &Client
|
|||||||
Vec::new()
|
Vec::new()
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to fetch updates from server. {}", e);
|
ctx.logger.warn(format!("Failed to fetch updates from server. {}", e));
|
||||||
Vec::new()
|
Vec::new()
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -63,12 +61,12 @@ async fn get_remote_updates(servers: &FxHashMap<String, String>, client: &Client
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a list of updates for all images passed in.
|
/// Returns a list of updates for all images passed in.
|
||||||
pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> Vec<Update> {
|
pub async fn get_updates(references: &Option<Vec<String>>, ctx: &Context) -> Vec<Update> {
|
||||||
let client = Client::new();
|
let client = Client::new(ctx);
|
||||||
|
|
||||||
// Get local images
|
// Get local images
|
||||||
debug!(config.debug, "Retrieving images to be checked");
|
ctx.logger.debug("Retrieving images to be checked");
|
||||||
let mut images = get_images_from_docker_daemon(config, references).await;
|
let mut images = get_images_from_docker_daemon(ctx, references).await;
|
||||||
|
|
||||||
// Add extra images from references
|
// Add extra images from references
|
||||||
if let Some(refs) = references {
|
if let Some(refs) = references {
|
||||||
@@ -82,18 +80,17 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get remote images from other servers
|
// Get remote images from other servers
|
||||||
let remote_updates = if !config.servers.is_empty() {
|
let remote_updates = if !ctx.config.servers.is_empty() {
|
||||||
debug!(config.debug, "Fetching updates from remote servers");
|
ctx.logger.debug("Fetching updates from remote servers");
|
||||||
get_remote_updates(&config.servers, &client).await
|
get_remote_updates(ctx, &client).await
|
||||||
} else {
|
} else {
|
||||||
Vec::new()
|
Vec::new()
|
||||||
};
|
};
|
||||||
|
|
||||||
debug!(
|
ctx.logger.debug(format!(
|
||||||
config.debug,
|
|
||||||
"Checking {:?}",
|
"Checking {:?}",
|
||||||
images.iter().map(|image| &image.reference).collect_vec()
|
images.iter().map(|image| &image.reference).collect_vec()
|
||||||
);
|
));
|
||||||
|
|
||||||
// Get a list of unique registries our images belong to. We are unwrapping the registry because it's guaranteed to be there.
|
// Get a list of unique registries our images belong to. We are unwrapping the registry because it's guaranteed to be there.
|
||||||
let registries: Vec<&String> = images
|
let registries: Vec<&String> = images
|
||||||
@@ -104,7 +101,7 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
|
|
||||||
// Create request client. All network requests share the same client for better performance.
|
// Create request client. All network requests share the same client for better performance.
|
||||||
// This client is also configured to retry a failed request up to 3 times with exponential backoff in between.
|
// This client is also configured to retry a failed request up to 3 times with exponential backoff in between.
|
||||||
let client = Client::new();
|
let client = Client::new(ctx);
|
||||||
|
|
||||||
// Create a map of images indexed by registry. This solution seems quite inefficient, since each iteration causes a key to be looked up. I can't find anything better at the moment.
|
// Create a map of images indexed by registry. This solution seems quite inefficient, since each iteration causes a key to be looked up. I can't find anything better at the moment.
|
||||||
let mut image_map: FxHashMap<&String, Vec<&Image>> = FxHashMap::default();
|
let mut image_map: FxHashMap<&String, Vec<&Image>> = FxHashMap::default();
|
||||||
@@ -119,12 +116,12 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
// Retrieve an authentication token (if required) for each registry.
|
// Retrieve an authentication token (if required) for each registry.
|
||||||
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
|
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
|
||||||
for registry in registries {
|
for registry in registries {
|
||||||
let credentials = if let Some(registry_config) = config.registries.get(registry) {
|
let credentials = if let Some(registry_config) = ctx.config.registries.get(registry) {
|
||||||
®istry_config.authentication
|
®istry_config.authentication
|
||||||
} else {
|
} else {
|
||||||
&None
|
&None
|
||||||
};
|
};
|
||||||
match check_auth(registry, config, &client).await {
|
match check_auth(registry, ctx, &client).await {
|
||||||
Some(auth_url) => {
|
Some(auth_url) => {
|
||||||
let token = get_token(
|
let token = get_token(
|
||||||
image_map.get(registry).unwrap(),
|
image_map.get(registry).unwrap(),
|
||||||
@@ -141,9 +138,10 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug!(config.debug, "Tokens: {:?}", tokens);
|
ctx.logger.debug(format!("Tokens: {:?}", tokens));
|
||||||
|
|
||||||
let ignored_registries = config
|
let ignored_registries = ctx
|
||||||
|
.config
|
||||||
.registries
|
.registries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(registry, registry_config)| {
|
.filter_map(|(registry, registry_config)| {
|
||||||
@@ -160,14 +158,15 @@ pub async fn get_updates(references: &Option<Vec<String>>, config: &Config) -> V
|
|||||||
// Loop through images check for updates
|
// Loop through images check for updates
|
||||||
for image in &images {
|
for image in &images {
|
||||||
let is_ignored = ignored_registries.contains(&&image.parts.registry)
|
let is_ignored = ignored_registries.contains(&&image.parts.registry)
|
||||||
|| config
|
|| ctx
|
||||||
|
.config
|
||||||
.images
|
.images
|
||||||
.exclude
|
.exclude
|
||||||
.iter()
|
.iter()
|
||||||
.any(|item| image.reference.starts_with(item));
|
.any(|item| image.reference.starts_with(item));
|
||||||
if !is_ignored {
|
if !is_ignored {
|
||||||
let token = tokens.get(image.parts.registry.as_str()).unwrap();
|
let token = tokens.get(image.parts.registry.as_str()).unwrap();
|
||||||
let future = image.check(token.as_deref(), config, &client);
|
let future = image.check(token.as_deref(), ctx, &client);
|
||||||
handles.push(future);
|
handles.push(future);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,8 +40,6 @@ pub struct ImageConfig {
|
|||||||
pub struct Config {
|
pub struct Config {
|
||||||
version: u8,
|
version: u8,
|
||||||
pub agent: bool,
|
pub agent: bool,
|
||||||
#[serde(skip_deserializing)]
|
|
||||||
pub debug: bool,
|
|
||||||
pub images: ImageConfig,
|
pub images: ImageConfig,
|
||||||
pub refresh_interval: Option<String>,
|
pub refresh_interval: Option<String>,
|
||||||
pub registries: FxHashMap<String, RegistryConfig>,
|
pub registries: FxHashMap<String, RegistryConfig>,
|
||||||
@@ -55,7 +53,6 @@ impl Config {
|
|||||||
Self {
|
Self {
|
||||||
version: 3,
|
version: 3,
|
||||||
agent: false,
|
agent: false,
|
||||||
debug: false,
|
|
||||||
images: ImageConfig::default(),
|
images: ImageConfig::default(),
|
||||||
refresh_interval: None,
|
refresh_interval: None,
|
||||||
registries: FxHashMap::default(),
|
registries: FxHashMap::default(),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use bollard::{models::ImageInspect, ClientVersion, Docker};
|
|||||||
|
|
||||||
use futures::future::join_all;
|
use futures::future::join_all;
|
||||||
|
|
||||||
use crate::{config::Config, error, structs::image::Image};
|
use crate::{error, structs::image::Image, Context};
|
||||||
|
|
||||||
fn create_docker_client(socket: Option<&str>) -> Docker {
|
fn create_docker_client(socket: Option<&str>) -> Docker {
|
||||||
let client: Result<Docker, bollard::errors::Error> = match socket {
|
let client: Result<Docker, bollard::errors::Error> = match socket {
|
||||||
@@ -38,10 +38,10 @@ fn create_docker_client(socket: Option<&str>) -> Docker {
|
|||||||
|
|
||||||
/// Retrieves images from Docker daemon. If `references` is Some, return only the images whose references match the ones specified.
|
/// Retrieves images from Docker daemon. If `references` is Some, return only the images whose references match the ones specified.
|
||||||
pub async fn get_images_from_docker_daemon(
|
pub async fn get_images_from_docker_daemon(
|
||||||
config: &Config,
|
ctx: &Context,
|
||||||
references: &Option<Vec<String>>,
|
references: &Option<Vec<String>>,
|
||||||
) -> Vec<Image> {
|
) -> Vec<Image> {
|
||||||
let client: Docker = create_docker_client(config.socket.as_deref());
|
let client: Docker = create_docker_client(ctx.config.socket.as_deref());
|
||||||
match references {
|
match references {
|
||||||
Some(refs) => {
|
Some(refs) => {
|
||||||
let mut inspect_handles = Vec::with_capacity(refs.len());
|
let mut inspect_handles = Vec::with_capacity(refs.len());
|
||||||
|
|||||||
20
src/http.rs
20
src/http.rs
@@ -4,7 +4,7 @@ use reqwest::Response;
|
|||||||
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
use reqwest_middleware::{ClientBuilder, ClientWithMiddleware};
|
||||||
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
use reqwest_retry::{policies::ExponentialBackoff, RetryTransientMiddleware};
|
||||||
|
|
||||||
use crate::{error, warn};
|
use crate::{error, Context};
|
||||||
|
|
||||||
pub enum RequestMethod {
|
pub enum RequestMethod {
|
||||||
GET,
|
GET,
|
||||||
@@ -23,16 +23,18 @@ impl Display for RequestMethod {
|
|||||||
/// A struct for handling HTTP requests. Takes care of the repetitive work of checking for errors, etc and exposes a simple interface
|
/// A struct for handling HTTP requests. Takes care of the repetitive work of checking for errors, etc and exposes a simple interface
|
||||||
pub struct Client {
|
pub struct Client {
|
||||||
inner: ClientWithMiddleware,
|
inner: ClientWithMiddleware,
|
||||||
|
ctx: Context,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Client {
|
impl Client {
|
||||||
pub fn new() -> Self {
|
pub fn new(ctx: &Context) -> Self {
|
||||||
Self {
|
Self {
|
||||||
inner: ClientBuilder::new(reqwest::Client::new())
|
inner: ClientBuilder::new(reqwest::Client::new())
|
||||||
.with(RetryTransientMiddleware::new_with_policy(
|
.with(RetryTransientMiddleware::new_with_policy(
|
||||||
ExponentialBackoff::builder().build_with_max_retries(3),
|
ExponentialBackoff::builder().build_with_max_retries(3),
|
||||||
))
|
))
|
||||||
.build(),
|
.build(),
|
||||||
|
ctx: ctx.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,14 +59,14 @@ impl Client {
|
|||||||
let status = response.status();
|
let status = response.status();
|
||||||
if status == 404 {
|
if status == 404 {
|
||||||
let message = format!("{} {}: Not found!", method, url);
|
let message = format!("{} {}: Not found!", method, url);
|
||||||
warn!("{}", message);
|
self.ctx.logger.warn(&message);
|
||||||
Err(message)
|
Err(message)
|
||||||
} else if status == 401 {
|
} else if status == 401 {
|
||||||
if ignore_401 {
|
if ignore_401 {
|
||||||
Ok(response)
|
Ok(response)
|
||||||
} else {
|
} else {
|
||||||
let message = format!("{} {}: Unauthorized! Please configure authentication for this registry or if you have already done so, please make sure it is correct.", method, url);
|
let message = format!("{} {}: Unauthorized! Please configure authentication for this registry or if you have already done so, please make sure it is correct.", method, url);
|
||||||
warn!("{}", message);
|
self.ctx.logger.warn(&message);
|
||||||
Err(message)
|
Err(message)
|
||||||
}
|
}
|
||||||
} else if status.as_u16() <= 400 {
|
} else if status.as_u16() <= 400 {
|
||||||
@@ -87,11 +89,11 @@ impl Client {
|
|||||||
Err(error) => {
|
Err(error) => {
|
||||||
if error.is_connect() {
|
if error.is_connect() {
|
||||||
let message = format!("{} {}: Connection failed!", method, url);
|
let message = format!("{} {}: Connection failed!", method, url);
|
||||||
warn!("{}", message);
|
self.ctx.logger.warn(&message);
|
||||||
Err(message)
|
Err(message)
|
||||||
} else if error.is_timeout() {
|
} else if error.is_timeout() {
|
||||||
let message = format!("{} {}: Connection timed out!", method, url);
|
let message = format!("{} {}: Connection timed out!", method, url);
|
||||||
warn!("{}", message);
|
self.ctx.logger.warn(&message);
|
||||||
Err(message)
|
Err(message)
|
||||||
} else {
|
} else {
|
||||||
error!(
|
error!(
|
||||||
@@ -123,9 +125,3 @@ impl Client {
|
|||||||
self.request(url, RequestMethod::HEAD, headers, false).await
|
self.request(url, RequestMethod::HEAD, headers, false).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Client {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
42
src/logging.rs
Normal file
42
src/logging.rs
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#[macro_export]
|
||||||
|
macro_rules! error {
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
eprintln!("\x1b[31;1mERROR\x1b[0m {}", format!($($arg)*));
|
||||||
|
std::process::exit(1);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This struct mostly exists so we can print stuff without passing debug or raw every time.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Logger {
|
||||||
|
debug: bool,
|
||||||
|
raw: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Logger {
|
||||||
|
pub fn new(debug: bool, raw: bool) -> Self {
|
||||||
|
Self { debug, raw }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn warn(&self, msg: impl AsRef<str>) {
|
||||||
|
if !self.raw {
|
||||||
|
eprintln!("\x1b[33;1m WARN\x1b[0m {}", msg.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(&self, msg: impl AsRef<str>) {
|
||||||
|
if !self.raw {
|
||||||
|
println!("\x1b[36;1m INFO\x1b[0m {}", msg.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug(&self, msg: impl AsRef<str>) {
|
||||||
|
if self.debug {
|
||||||
|
println!("\x1b[35;1mDEBUG\x1b[0m {}", msg.as_ref());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_raw(&mut self, raw: bool) {
|
||||||
|
self.raw = raw
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/main.rs
26
src/main.rs
@@ -4,6 +4,7 @@ use config::Config;
|
|||||||
use formatting::spinner::Spinner;
|
use formatting::spinner::Spinner;
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
use formatting::{print_raw_updates, print_updates};
|
use formatting::{print_raw_updates, print_updates};
|
||||||
|
use logging::Logger;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
use server::serve;
|
use server::serve;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@@ -15,6 +16,7 @@ pub mod docker;
|
|||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
pub mod formatting;
|
pub mod formatting;
|
||||||
pub mod http;
|
pub mod http;
|
||||||
|
pub mod logging;
|
||||||
pub mod registry;
|
pub mod registry;
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
pub mod server;
|
pub mod server;
|
||||||
@@ -62,6 +64,12 @@ enum Commands {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Context {
|
||||||
|
pub config: Config,
|
||||||
|
pub logger: Logger,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let cli = Cli::parse();
|
let cli = Cli::parse();
|
||||||
@@ -73,7 +81,10 @@ async fn main() {
|
|||||||
if let Some(socket) = cli.socket {
|
if let Some(socket) = cli.socket {
|
||||||
config.socket = Some(socket)
|
config.socket = Some(socket)
|
||||||
}
|
}
|
||||||
config.debug = cli.debug;
|
let mut ctx = Context {
|
||||||
|
config,
|
||||||
|
logger: Logger::new(cli.debug, false),
|
||||||
|
};
|
||||||
match &cli.command {
|
match &cli.command {
|
||||||
#[cfg(feature = "cli")]
|
#[cfg(feature = "cli")]
|
||||||
Some(Commands::Check {
|
Some(Commands::Check {
|
||||||
@@ -82,23 +93,26 @@ async fn main() {
|
|||||||
raw,
|
raw,
|
||||||
}) => {
|
}) => {
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
match *raw || config.debug {
|
if *raw {
|
||||||
|
ctx.logger.set_raw(true);
|
||||||
|
}
|
||||||
|
match *raw || cli.debug {
|
||||||
true => {
|
true => {
|
||||||
let updates = get_updates(references, &config).await;
|
let updates = get_updates(references, &ctx).await;
|
||||||
print_raw_updates(&updates);
|
print_raw_updates(&updates);
|
||||||
}
|
}
|
||||||
false => {
|
false => {
|
||||||
let spinner = Spinner::new();
|
let spinner = Spinner::new();
|
||||||
let updates = get_updates(references, &config).await;
|
let updates = get_updates(references, &ctx).await;
|
||||||
spinner.succeed();
|
spinner.succeed();
|
||||||
print_updates(&updates, icons);
|
print_updates(&updates, icons);
|
||||||
info!("✨ Checked {} images in {}ms", updates.len(), start.elapsed().unwrap().as_millis());
|
ctx.logger.info(format!("✨ Checked {} images in {}ms", updates.len(), start.elapsed().unwrap().as_millis()));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
Some(Commands::Serve { port }) => {
|
Some(Commands::Serve { port }) => {
|
||||||
let _ = serve(port, &config).await;
|
let _ = serve(port, &ctx).await;
|
||||||
}
|
}
|
||||||
None => error!("Whoops! It looks like you haven't specified a command to run! Try `cup help` to see available options."),
|
None => error!("Whoops! It looks like you haven't specified a command to run! Try `cup help` to see available options."),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,7 @@ use std::time::SystemTime;
|
|||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
error,
|
||||||
debug, error,
|
|
||||||
http::Client,
|
http::Client,
|
||||||
structs::{
|
structs::{
|
||||||
image::{DigestInfo, Image, VersionInfo},
|
image::{DigestInfo, Image, VersionInfo},
|
||||||
@@ -17,10 +16,11 @@ use crate::{
|
|||||||
},
|
},
|
||||||
time::{elapsed, now},
|
time::{elapsed, now},
|
||||||
},
|
},
|
||||||
|
Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub async fn check_auth(registry: &str, config: &Config, client: &Client) -> Option<String> {
|
pub async fn check_auth(registry: &str, ctx: &Context, client: &Client) -> Option<String> {
|
||||||
let protocol = get_protocol(registry, &config.registries);
|
let protocol = get_protocol(registry, &ctx.config.registries);
|
||||||
let url = format!("{}://{}/v2/", protocol, registry);
|
let url = format!("{}://{}/v2/", protocol, registry);
|
||||||
let response = client.get(&url, Vec::new(), true).await;
|
let response = client.get(&url, Vec::new(), true).await;
|
||||||
match response {
|
match response {
|
||||||
@@ -45,15 +45,13 @@ pub async fn check_auth(registry: &str, config: &Config, client: &Client) -> Opt
|
|||||||
pub async fn get_latest_digest(
|
pub async fn get_latest_digest(
|
||||||
image: &Image,
|
image: &Image,
|
||||||
token: Option<&str>,
|
token: Option<&str>,
|
||||||
config: &Config,
|
ctx: &Context,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> Image {
|
) -> Image {
|
||||||
debug!(
|
ctx.logger
|
||||||
config.debug,
|
.debug(format!("Checking for digest update to {}", image.reference));
|
||||||
"Checking for digest update to {}", image.reference
|
|
||||||
);
|
|
||||||
let start = SystemTime::now();
|
let start = SystemTime::now();
|
||||||
let protocol = get_protocol(&image.parts.registry, &config.registries);
|
let protocol = get_protocol(&image.parts.registry, &ctx.config.registries);
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}://{}/v2/{}/manifests/{}",
|
"{}://{}/v2/{}/manifests/{}",
|
||||||
protocol, &image.parts.registry, &image.parts.repository, &image.parts.tag
|
protocol, &image.parts.registry, &image.parts.repository, &image.parts.tag
|
||||||
@@ -63,10 +61,10 @@ pub async fn get_latest_digest(
|
|||||||
|
|
||||||
let response = client.head(&url, headers).await;
|
let response = client.head(&url, headers).await;
|
||||||
let time = start.elapsed().unwrap().as_millis() as u32;
|
let time = start.elapsed().unwrap().as_millis() as u32;
|
||||||
debug!(
|
ctx.logger.debug(format!(
|
||||||
config.debug,
|
"Checked for digest update to {} in {}ms",
|
||||||
"Checked for digest update to {} in {}ms", image.reference, time
|
image.reference, time
|
||||||
);
|
));
|
||||||
match response {
|
match response {
|
||||||
Ok(res) => match res.headers().get("docker-content-digest") {
|
Ok(res) => match res.headers().get("docker-content-digest") {
|
||||||
Some(digest) => {
|
Some(digest) => {
|
||||||
@@ -121,15 +119,13 @@ pub async fn get_latest_tag(
|
|||||||
image: &Image,
|
image: &Image,
|
||||||
base: &Version,
|
base: &Version,
|
||||||
token: Option<&str>,
|
token: Option<&str>,
|
||||||
config: &Config,
|
ctx: &Context,
|
||||||
client: &Client,
|
client: &Client,
|
||||||
) -> Image {
|
) -> Image {
|
||||||
debug!(
|
ctx.logger
|
||||||
config.debug,
|
.debug(format!("Checking for tag update to {}", image.reference));
|
||||||
"Checking for tag update to {}", image.reference
|
|
||||||
);
|
|
||||||
let start = now();
|
let start = now();
|
||||||
let protocol = get_protocol(&image.parts.registry, &config.registries);
|
let protocol = get_protocol(&image.parts.registry, &ctx.config.registries);
|
||||||
let url = format!(
|
let url = format!(
|
||||||
"{}://{}/v2/{}/tags/list",
|
"{}://{}/v2/{}/tags/list",
|
||||||
protocol, &image.parts.registry, &image.parts.repository,
|
protocol, &image.parts.registry, &image.parts.repository,
|
||||||
@@ -144,12 +140,11 @@ pub async fn get_latest_tag(
|
|||||||
let mut next_url = Some(url);
|
let mut next_url = Some(url);
|
||||||
|
|
||||||
while next_url.is_some() {
|
while next_url.is_some() {
|
||||||
debug!(
|
ctx.logger.debug(format!(
|
||||||
config.debug,
|
|
||||||
"{} has extra tags! Current number of valid tags: {}",
|
"{} has extra tags! Current number of valid tags: {}",
|
||||||
image.reference,
|
image.reference,
|
||||||
tags.len()
|
tags.len()
|
||||||
);
|
));
|
||||||
let (new_tags, next) = match get_extra_tags(
|
let (new_tags, next) = match get_extra_tags(
|
||||||
&next_url.unwrap(),
|
&next_url.unwrap(),
|
||||||
headers.clone(),
|
headers.clone(),
|
||||||
@@ -172,20 +167,19 @@ pub async fn get_latest_tag(
|
|||||||
next_url = next;
|
next_url = next;
|
||||||
}
|
}
|
||||||
let tag = tags.iter().max();
|
let tag = tags.iter().max();
|
||||||
debug!(
|
ctx.logger.debug(format!(
|
||||||
config.debug,
|
|
||||||
"Checked for tag update to {} in {}ms",
|
"Checked for tag update to {} in {}ms",
|
||||||
image.reference,
|
image.reference,
|
||||||
elapsed(start)
|
elapsed(start)
|
||||||
);
|
));
|
||||||
match tag {
|
match tag {
|
||||||
Some(t) => {
|
Some(t) => {
|
||||||
if t == base && image.digest_info.is_some() {
|
if t == base && image.digest_info.is_some() {
|
||||||
// Tags are equal so we'll compare digests
|
// Tags are equal so we'll compare digests
|
||||||
debug!(
|
ctx.logger.debug(format!(
|
||||||
config.debug,
|
"Tags for {} are equal, comparing digests.",
|
||||||
"Tags for {} are equal, comparing digests.", image.reference
|
image.reference
|
||||||
);
|
));
|
||||||
get_latest_digest(
|
get_latest_digest(
|
||||||
&Image {
|
&Image {
|
||||||
version_info: Some(VersionInfo {
|
version_info: Some(VersionInfo {
|
||||||
@@ -196,7 +190,7 @@ pub async fn get_latest_tag(
|
|||||||
..image.clone()
|
..image.clone()
|
||||||
},
|
},
|
||||||
token,
|
token,
|
||||||
config,
|
ctx,
|
||||||
client,
|
client,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ use xitca_web::{
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
check::get_updates,
|
check::get_updates,
|
||||||
config::{Config, Theme},
|
config::Theme,
|
||||||
info,
|
|
||||||
structs::update::Update,
|
structs::update::Update,
|
||||||
utils::{
|
utils::{
|
||||||
json::{to_full_json, to_simple_json},
|
json::{to_full_json, to_simple_json},
|
||||||
sort_update_vec::sort_update_vec,
|
sort_update_vec::sort_update_vec,
|
||||||
time::{elapsed, now},
|
time::{elapsed, now},
|
||||||
},
|
},
|
||||||
|
Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
const HTML: &str = include_str!("static/index.html");
|
const HTML: &str = include_str!("static/index.html");
|
||||||
@@ -46,13 +46,13 @@ const SORT_ORDER: [&str; 8] = [
|
|||||||
"unknown",
|
"unknown",
|
||||||
]; // For Liquid rendering
|
]; // For Liquid rendering
|
||||||
|
|
||||||
pub async fn serve(port: &u16, config: &Config) -> std::io::Result<()> {
|
pub async fn serve(port: &u16, ctx: &Context) -> std::io::Result<()> {
|
||||||
info!("Starting server, please wait...");
|
ctx.logger.info("Starting server, please wait...");
|
||||||
let data = ServerData::new(config).await;
|
let data = ServerData::new(ctx).await;
|
||||||
let scheduler = JobScheduler::new().await.unwrap();
|
let scheduler = JobScheduler::new().await.unwrap();
|
||||||
let data = Arc::new(Mutex::new(data));
|
let data = Arc::new(Mutex::new(data));
|
||||||
let data_copy = data.clone();
|
let data_copy = data.clone();
|
||||||
if let Some(interval) = &config.refresh_interval {
|
if let Some(interval) = &ctx.config.refresh_interval {
|
||||||
scheduler
|
scheduler
|
||||||
.add(
|
.add(
|
||||||
Job::new_async(interval, move |_uuid, _lock| {
|
Job::new_async(interval, move |_uuid, _lock| {
|
||||||
@@ -67,14 +67,14 @@ pub async fn serve(port: &u16, config: &Config) -> std::io::Result<()> {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
scheduler.start().await.unwrap();
|
scheduler.start().await.unwrap();
|
||||||
info!("Ready to start!");
|
ctx.logger.info("Ready to start!");
|
||||||
let mut app_builder = App::new()
|
let mut app_builder = App::new()
|
||||||
.with_state(data)
|
.with_state(data)
|
||||||
.at("/api/v2/json", get(handler_service(api_simple)))
|
.at("/api/v2/json", get(handler_service(api_simple)))
|
||||||
.at("/api/v3/json", get(handler_service(api_full)))
|
.at("/api/v3/json", get(handler_service(api_full)))
|
||||||
.at("/api/v2/refresh", get(handler_service(refresh)))
|
.at("/api/v2/refresh", get(handler_service(refresh)))
|
||||||
.at("/api/v3/refresh", get(handler_service(refresh)));
|
.at("/api/v3/refresh", get(handler_service(refresh)));
|
||||||
if !config.agent {
|
if !ctx.config.agent {
|
||||||
app_builder = app_builder
|
app_builder = app_builder
|
||||||
.at("/", get(handler_service(_static)))
|
.at("/", get(handler_service(_static)))
|
||||||
.at("/*", get(handler_service(_static)));
|
.at("/*", get(handler_service(_static)));
|
||||||
@@ -151,14 +151,14 @@ struct ServerData {
|
|||||||
raw_updates: Vec<Update>,
|
raw_updates: Vec<Update>,
|
||||||
simple_json: Value,
|
simple_json: Value,
|
||||||
full_json: Value,
|
full_json: Value,
|
||||||
config: Config,
|
ctx: Context,
|
||||||
theme: &'static str,
|
theme: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ServerData {
|
impl ServerData {
|
||||||
async fn new(config: &Config) -> Self {
|
async fn new(ctx: &Context) -> Self {
|
||||||
let mut s = Self {
|
let mut s = Self {
|
||||||
config: config.clone(),
|
ctx: ctx.clone(),
|
||||||
template: String::new(),
|
template: String::new(),
|
||||||
simple_json: Value::Null,
|
simple_json: Value::Null,
|
||||||
full_json: Value::Null,
|
full_json: Value::Null,
|
||||||
@@ -171,14 +171,14 @@ impl ServerData {
|
|||||||
async fn refresh(&mut self) {
|
async fn refresh(&mut self) {
|
||||||
let start = now();
|
let start = now();
|
||||||
if !self.raw_updates.is_empty() {
|
if !self.raw_updates.is_empty() {
|
||||||
info!("Refreshing data");
|
self.ctx.logger.info("Refreshing data");
|
||||||
}
|
}
|
||||||
let updates = sort_update_vec(&get_updates(&None, &self.config).await);
|
let updates = sort_update_vec(&get_updates(&None, &self.ctx).await);
|
||||||
info!(
|
self.ctx.logger.info(format!(
|
||||||
"✨ Checked {} images in {}ms",
|
"✨ Checked {} images in {}ms",
|
||||||
updates.len(),
|
updates.len(),
|
||||||
elapsed(start)
|
elapsed(start)
|
||||||
);
|
));
|
||||||
self.raw_updates = updates;
|
self.raw_updates = updates;
|
||||||
let template = liquid::ParserBuilder::with_stdlib()
|
let template = liquid::ParserBuilder::with_stdlib()
|
||||||
.build()
|
.build()
|
||||||
@@ -193,7 +193,7 @@ impl ServerData {
|
|||||||
.to_string()
|
.to_string()
|
||||||
.into();
|
.into();
|
||||||
self.full_json["last_updated"] = self.simple_json["last_updated"].clone();
|
self.full_json["last_updated"] = self.simple_json["last_updated"].clone();
|
||||||
self.theme = match &self.config.theme {
|
self.theme = match &self.ctx.config.theme {
|
||||||
Theme::Default => "neutral",
|
Theme::Default => "neutral",
|
||||||
Theme::Blue => "gray",
|
Theme::Blue => "gray",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
config::Config,
|
|
||||||
error,
|
error,
|
||||||
http::Client,
|
http::Client,
|
||||||
registry::{get_latest_digest, get_latest_tag},
|
registry::{get_latest_digest, get_latest_tag},
|
||||||
structs::{status::Status, version::Version},
|
structs::{status::Status, version::Version},
|
||||||
utils::reference::split,
|
utils::reference::split,
|
||||||
|
Context,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
@@ -168,8 +168,20 @@ impl Image {
|
|||||||
.replacen("{}", &new_tag.minor.unwrap_or(0).to_string(), 1)
|
.replacen("{}", &new_tag.minor.unwrap_or(0).to_string(), 1)
|
||||||
.replacen("{}", &new_tag.patch.unwrap_or(0).to_string(), 1),
|
.replacen("{}", &new_tag.patch.unwrap_or(0).to_string(), 1),
|
||||||
// Throwing these in, because they're useful for the CLI output, however we won't (de)serialize them
|
// Throwing these in, because they're useful for the CLI output, however we won't (de)serialize them
|
||||||
current_version: self.version_info.as_ref().unwrap().current_tag.to_string(),
|
current_version: self
|
||||||
new_version: self.version_info.as_ref().unwrap().latest_remote_tag.as_ref().unwrap().to_string()
|
.version_info
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.current_tag
|
||||||
|
.to_string(),
|
||||||
|
new_version: self
|
||||||
|
.version_info
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.latest_remote_tag
|
||||||
|
.as_ref()
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
"digest" => {
|
"digest" => {
|
||||||
@@ -185,7 +197,7 @@ impl Image {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
"none" => UpdateInfo::None,
|
"none" => UpdateInfo::None,
|
||||||
_ => unreachable!()
|
_ => unreachable!(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
error: self.error.clone(),
|
error: self.error.clone(),
|
||||||
@@ -197,11 +209,11 @@ impl Image {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the image has an update
|
/// Checks if the image has an update
|
||||||
pub async fn check(&self, token: Option<&str>, config: &Config, client: &Client) -> Self {
|
pub async fn check(&self, token: Option<&str>, ctx: &Context, client: &Client) -> Self {
|
||||||
match &self.version_info {
|
match &self.version_info {
|
||||||
Some(data) => get_latest_tag(self, &data.current_tag, token, config, client).await,
|
Some(data) => get_latest_tag(self, &data.current_tag, token, ctx, client).await,
|
||||||
None => match self.digest_info {
|
None => match self.digest_info {
|
||||||
Some(_) => get_latest_digest(self, token, config, client).await,
|
Some(_) => get_latest_digest(self, token, ctx, client).await,
|
||||||
None => unreachable!(),
|
None => unreachable!(),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,34 +0,0 @@
|
|||||||
// Logging utilites
|
|
||||||
|
|
||||||
/// 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[31;1mERROR\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[33;1mWARN \x1b[0m {}", format!($($arg)*));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! info {
|
|
||||||
($($arg:tt)*) => ({
|
|
||||||
println!("\x1b[36;1mINFO \x1b[0m {}", format!($($arg)*));
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! debug {
|
|
||||||
($debg:expr, $($arg:tt)*) => ({
|
|
||||||
if $debg {
|
|
||||||
println!("\x1b[35;1mDEBUG\x1b[0m {}", format!($($arg)*));
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
pub mod json;
|
pub mod json;
|
||||||
pub mod link;
|
pub mod link;
|
||||||
pub mod logging;
|
|
||||||
pub mod reference;
|
pub mod reference;
|
||||||
pub mod request;
|
pub mod request;
|
||||||
pub mod sort_update_vec;
|
pub mod sort_update_vec;
|
||||||
|
|||||||
Reference in New Issue
Block a user