mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-15 08:33:49 -05:00
Refactor and simplify server code, UI updates requested in #5 and #6
Some checks failed
Nightly Release / build-binary (map[bin:cup command:build name:cup-linux-aarch64 os:ubuntu-latest release_for:linux-aarch64 target:aarch64-unknown-linux-musl]) (push) Has been cancelled
Nightly Release / build-binary (map[bin:cup command:build name:cup-linux-x86_64 os:ubuntu-latest release_for:linux-x86_64 target:x86_64-unknown-linux-musl]) (push) Has been cancelled
Nightly Release / build-image (push) Has been cancelled
Some checks failed
Nightly Release / build-binary (map[bin:cup command:build name:cup-linux-aarch64 os:ubuntu-latest release_for:linux-aarch64 target:aarch64-unknown-linux-musl]) (push) Has been cancelled
Nightly Release / build-binary (map[bin:cup command:build name:cup-linux-x86_64 os:ubuntu-latest release_for:linux-x86_64 target:x86_64-unknown-linux-musl]) (push) Has been cancelled
Nightly Release / build-image (push) Has been cancelled
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -353,6 +353,7 @@ name = "cup"
|
|||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bollard",
|
"bollard",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"http-auth",
|
"http-auth",
|
||||||
"indicatif",
|
"indicatif",
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ once_cell = "1.19.0"
|
|||||||
http-auth = { version = "0.1.9", features = [] }
|
http-auth = { version = "0.1.9", 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"] }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["server", "cli"]
|
default = ["server", "cli"]
|
||||||
|
|||||||
@@ -95,8 +95,7 @@ async fn main() {
|
|||||||
},
|
},
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
Some(Commands::Serve { port }) => {
|
Some(Commands::Serve { port }) => {
|
||||||
let updates = get_all_updates(cli.socket).await;
|
let _ = serve(port, cli.socket).await;
|
||||||
let _ = serve(port, &updates).await;
|
|
||||||
}
|
}
|
||||||
None => (),
|
None => (),
|
||||||
}
|
}
|
||||||
|
|||||||
149
src/server.rs
149
src/server.rs
@@ -1,27 +1,33 @@
|
|||||||
use std::sync::Mutex;
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use chrono::Local;
|
||||||
use liquid::{object, Object};
|
use liquid::{object, Object};
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use xitca_web::{
|
use xitca_web::{
|
||||||
body::ResponseBody,
|
body::ResponseBody,
|
||||||
handler::{handler_service, state::StateOwn},
|
handler::{handler_service, state::StateRef},
|
||||||
http::WebResponse,
|
http::WebResponse,
|
||||||
|
middleware::Logger,
|
||||||
route::get,
|
route::get,
|
||||||
App,
|
App,
|
||||||
middleware::Logger
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{get_all_updates, utils::sort_update_vec};
|
||||||
|
|
||||||
const RAW_TEMPLATE: &str = include_str!("static/template.liquid");
|
const RAW_TEMPLATE: &str = include_str!("static/template.liquid");
|
||||||
const STYLE: &str = include_str!("static/index.css");
|
const STYLE: &str = include_str!("static/index.css");
|
||||||
const FAVICON_ICO: &[u8] = include_bytes!("static/favicon.ico");
|
const FAVICON_ICO: &[u8] = include_bytes!("static/favicon.ico");
|
||||||
const FAVICON_SVG: &[u8] = include_bytes!("static/favicon.svg");
|
const FAVICON_SVG: &[u8] = include_bytes!("static/favicon.svg");
|
||||||
const APPLE_TOUCH_ICON: &[u8] = include_bytes!("static/apple-touch-icon.png");
|
const APPLE_TOUCH_ICON: &[u8] = include_bytes!("static/apple-touch-icon.png");
|
||||||
|
|
||||||
pub async fn serve(port: &u16, updates: &[(String, Option<bool>)]) -> std::io::Result<()> {
|
pub async fn serve(port: &u16, socket: Option<String>) -> std::io::Result<()> {
|
||||||
|
let mut data = UpdateData::new(socket).await;
|
||||||
|
data.refresh().await;
|
||||||
App::new()
|
App::new()
|
||||||
.with_state(updates.to_owned())
|
.with_state(Arc::new(Mutex::new(data)))
|
||||||
.at("/", get(handler_service(home)))
|
.at("/", get(handler_service(home)))
|
||||||
.at("/json", get(handler_service(json)))
|
.at("/json", get(handler_service(json)))
|
||||||
|
.at("/refresh", get(handler_service(refresh)))
|
||||||
.at("/favicon.ico", handler_service(favicon_ico)) // These aren't pretty but this is xitca-web...
|
.at("/favicon.ico", handler_service(favicon_ico)) // These aren't pretty but this is xitca-web...
|
||||||
.at("/favicon.svg", handler_service(favicon_svg))
|
.at("/favicon.svg", handler_service(favicon_svg))
|
||||||
.at("/apple-touch-icon.png", handler_service(apple_touch_icon))
|
.at("/apple-touch-icon.png", handler_service(apple_touch_icon))
|
||||||
@@ -32,62 +38,17 @@ pub async fn serve(port: &u16, updates: &[(String, Option<bool>)]) -> std::io::R
|
|||||||
.wait()
|
.wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn home(
|
async fn home(data: StateRef<'_, Arc<Mutex<UpdateData>>>) -> WebResponse {
|
||||||
updates: StateOwn<Vec<(String, Option<bool>)>>,
|
WebResponse::new(ResponseBody::from(data.lock().unwrap().template.clone()))
|
||||||
) -> WebResponse {
|
|
||||||
let template = liquid::ParserBuilder::with_stdlib()
|
|
||||||
.build()
|
|
||||||
.unwrap()
|
|
||||||
.parse(RAW_TEMPLATE)
|
|
||||||
.unwrap();
|
|
||||||
let images = updates
|
|
||||||
.0
|
|
||||||
.par_iter()
|
|
||||||
.map(|(name, image)| match image {
|
|
||||||
Some(value) => {
|
|
||||||
if *value {
|
|
||||||
object!({"name": name, "status": "update-available"})
|
|
||||||
} else {
|
|
||||||
object!({"name": name, "status": "up-to-date"})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => object!({"name": name, "status": "unknown"}),
|
|
||||||
})
|
|
||||||
.collect::<Vec<Object>>();
|
|
||||||
let uptodate = images
|
|
||||||
.par_iter()
|
|
||||||
.filter(|&o| o["status"] == "up-to-date")
|
|
||||||
.collect::<Vec<&Object>>()
|
|
||||||
.len();
|
|
||||||
let updatable = images
|
|
||||||
.par_iter()
|
|
||||||
.filter(|&o| o["status"] == "update-available")
|
|
||||||
.collect::<Vec<&Object>>()
|
|
||||||
.len();
|
|
||||||
let unknown = images
|
|
||||||
.par_iter()
|
|
||||||
.filter(|&o| o["status"] == "unknown")
|
|
||||||
.collect::<Vec<&Object>>()
|
|
||||||
.len();
|
|
||||||
let globals = object!({
|
|
||||||
"metrics": [{"name": "Monitored images", "value": images.len()}, {"name": "Up to date", "value": uptodate}, {"name": "Updates available", "value": updatable}, {"name": "Unknown", "value": unknown}],
|
|
||||||
"images": images,
|
|
||||||
"style": STYLE
|
|
||||||
});
|
|
||||||
let result = template.render(&globals).unwrap();
|
|
||||||
WebResponse::new(ResponseBody::from(result))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn json(
|
async fn json(data: StateRef<'_, Arc<Mutex<UpdateData>>>) -> WebResponse {
|
||||||
updates: StateOwn<Vec<(String, Option<bool>)>>
|
WebResponse::new(ResponseBody::from(data.lock().unwrap().json.clone()))
|
||||||
) -> WebResponse {
|
}
|
||||||
let result_mutex: Mutex<json::object::Object> = Mutex::new(json::object::Object::new());
|
|
||||||
updates.par_iter().for_each(|image| match image.1 {
|
async fn refresh(data: StateRef<'_, Arc<Mutex<UpdateData>>>) -> WebResponse {
|
||||||
Some(b) => result_mutex.lock().unwrap().insert(&image.0, json::from(b)),
|
data.lock().unwrap().refresh().await;
|
||||||
None => result_mutex.lock().unwrap().insert(&image.0, json::Null),
|
return WebResponse::new(ResponseBody::from("OK"));
|
||||||
});
|
|
||||||
let result = json::stringify(result_mutex.lock().unwrap().clone());
|
|
||||||
WebResponse::new(ResponseBody::from(result))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn favicon_ico() -> WebResponse {
|
async fn favicon_ico() -> WebResponse {
|
||||||
@@ -101,3 +62,73 @@ async fn favicon_svg() -> WebResponse {
|
|||||||
async fn apple_touch_icon() -> WebResponse {
|
async fn apple_touch_icon() -> WebResponse {
|
||||||
WebResponse::new(ResponseBody::from(APPLE_TOUCH_ICON))
|
WebResponse::new(ResponseBody::from(APPLE_TOUCH_ICON))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct UpdateData {
|
||||||
|
template: String,
|
||||||
|
raw: Vec<(String, Option<bool>)>,
|
||||||
|
json: String,
|
||||||
|
socket: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UpdateData {
|
||||||
|
async fn new(socket: Option<String>) -> Self {
|
||||||
|
return Self {
|
||||||
|
socket,
|
||||||
|
template: String::new(),
|
||||||
|
json: String::new(),
|
||||||
|
raw: Vec::new(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
async fn refresh(self: &mut Self) {
|
||||||
|
let updates = sort_update_vec(&get_all_updates(self.socket.clone()).await);
|
||||||
|
self.raw = updates;
|
||||||
|
let template = liquid::ParserBuilder::with_stdlib()
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.parse(RAW_TEMPLATE)
|
||||||
|
.unwrap();
|
||||||
|
let images = self
|
||||||
|
.raw
|
||||||
|
.iter()
|
||||||
|
.map(|(name, image)| match image {
|
||||||
|
Some(value) => {
|
||||||
|
if *value {
|
||||||
|
object!({"name": name, "status": "update-available"})
|
||||||
|
} else {
|
||||||
|
object!({"name": name, "status": "up-to-date"})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => object!({"name": name, "status": "unknown"}),
|
||||||
|
})
|
||||||
|
.collect::<Vec<Object>>();
|
||||||
|
let uptodate = images
|
||||||
|
.par_iter()
|
||||||
|
.filter(|&o| o["status"] == "up-to-date")
|
||||||
|
.collect::<Vec<&Object>>()
|
||||||
|
.len();
|
||||||
|
let updatable = images
|
||||||
|
.par_iter()
|
||||||
|
.filter(|&o| o["status"] == "update-available")
|
||||||
|
.collect::<Vec<&Object>>()
|
||||||
|
.len();
|
||||||
|
let unknown = images
|
||||||
|
.par_iter()
|
||||||
|
.filter(|&o| o["status"] == "unknown")
|
||||||
|
.collect::<Vec<&Object>>()
|
||||||
|
.len();
|
||||||
|
let last_updated = Local::now().format("%Y-%m-%d %H:%M:%S");
|
||||||
|
let globals = object!({
|
||||||
|
"metrics": [{"name": "Monitored images", "value": images.len()}, {"name": "Up to date", "value": uptodate}, {"name": "Updates available", "value": updatable}, {"name": "Unknown", "value": unknown}],
|
||||||
|
"images": images,
|
||||||
|
"style": STYLE,
|
||||||
|
"last_updated": last_updated.to_string()
|
||||||
|
});
|
||||||
|
self.template = template.render(&globals).unwrap();
|
||||||
|
let json_data: Mutex<json::object::Object> = Mutex::new(json::object::Object::new());
|
||||||
|
self.raw.par_iter().for_each(|image| match image.1 {
|
||||||
|
Some(b) => json_data.lock().unwrap().insert(&image.0, json::from(b)),
|
||||||
|
None => json_data.lock().unwrap().insert(&image.0, json::Null),
|
||||||
|
});
|
||||||
|
self.json = json::stringify(json_data.lock().unwrap().clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -6,7 +6,7 @@
|
|||||||
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
<link rel="icon" type="image/svg+xml" href="favicon.svg">
|
||||||
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
<link rel="apple-touch-icon" href="apple-touch-icon.png">
|
||||||
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
<style>
|
<style>
|
||||||
{{ style }}
|
{{ style }}
|
||||||
</style>
|
</style>
|
||||||
@@ -70,13 +70,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<script>
|
||||||
|
function refresh(event) {
|
||||||
|
var button = event.currentTarget;
|
||||||
|
button.disabled = true;
|
||||||
|
|
||||||
|
let request = new XMLHttpRequest()
|
||||||
|
request.onload = function () {
|
||||||
|
if (request.status === 200) {
|
||||||
|
window.location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
request.open("GET", `${window.location.origin}/refresh`);
|
||||||
|
request.send();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950">
|
<div class="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950">
|
||||||
<div class="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full">
|
<div class="lg:px-8 sm:px-6 px-4 max-w-[80rem] mx-auto h-full w-full">
|
||||||
<div class="max-w-[48rem] mx-auto h-full my-8">
|
<div class="max-w-[48rem] mx-auto h-full my-8">
|
||||||
<div class="flex items-center gap-1">
|
<div class="flex items-center gap-1">
|
||||||
<h1 class="text-6xl font-bold dark:text-white">Cup</h1>
|
<h1 class="text-5xl lg:text-6xl font-bold dark:text-white">Cup</h1>
|
||||||
<svg
|
<svg
|
||||||
version="1.1"
|
version="1.1"
|
||||||
id="Layer_2"
|
id="Layer_2"
|
||||||
@@ -120,7 +135,57 @@
|
|||||||
{% for metric in metrics %}
|
{% for metric in metrics %}
|
||||||
<div class="gi">
|
<div class="gi">
|
||||||
<div class="xl:px-8 px-6 py-4 gap-y-2 gap-x-4 justify-between align-baseline flex flex-col h-full">
|
<div class="xl:px-8 px-6 py-4 gap-y-2 gap-x-4 justify-between align-baseline flex flex-col h-full">
|
||||||
<dt class="text-gray-500 dark:text-gray-400 leading-6 font-medium">{{ metric.name }}</dt>
|
<dt class="text-gray-500 dark:text-gray-400 leading-6 font-medium flex gap-1 justify-between">
|
||||||
|
{{ metric.name }}
|
||||||
|
{% if metric.name == 'Monitored images' %}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
class="size-6 text-black dark:text-white shrink-0"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 4c4.29 0 7.863 2.429 10.665 7.154l.22 .379l.045 .1l.03 .083l.014 .055l.014 .082l.011 .1v.11l-.014 .111a.992 .992 0 0 1 -.026 .11l-.039 .108l-.036 .075l-.016 .03c-2.764 4.836 -6.3 7.38 -10.555 7.499l-.313 .004c-4.396 0 -8.037 -2.549 -10.868 -7.504a1 1 0 0 1 0 -.992c2.831 -4.955 6.472 -7.504 10.868 -7.504zm0 5a3 3 0 1 0 0 6a3 3 0 0 0 0 -6z" />
|
||||||
|
</svg>
|
||||||
|
{% elsif metric.name == 'Up to date' %}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
class="size-6 text-green-500 shrink-0"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-1.293 5.953a1 1 0 0 0 -1.32 -.083l-.094 .083l-3.293 3.292l-1.293 -1.292l-.094 -.083a1 1 0 0 0 -1.403 1.403l.083 .094l2 2l.094 .083a1 1 0 0 0 1.226 0l.094 -.083l4 -4l.083 -.094a1 1 0 0 0 -.083 -1.32z" />
|
||||||
|
</svg>
|
||||||
|
{% elsif metric.name == 'Updates available' %}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
class="size-6 text-blue-500 shrink-0"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M17 3.34a10 10 0 1 1 -14.995 8.984l-.005 -.324l.005 -.324a10 10 0 0 1 14.995 -8.336zm-4.98 3.66l-.163 .01l-.086 .016l-.142 .045l-.113 .054l-.07 .043l-.095 .071l-.058 .054l-4 4l-.083 .094a1 1 0 0 0 1.497 1.32l2.293 -2.293v5.586l.007 .117a1 1 0 0 0 1.993 -.117v-5.585l2.293 2.292l.094 .083a1 1 0 0 0 1.32 -1.497l-4 -4l-.082 -.073l-.089 -.064l-.113 -.062l-.081 -.034l-.113 -.034l-.112 -.02l-.098 -.006z" />
|
||||||
|
</svg>
|
||||||
|
{% elsif metric.name == 'Unknown' %}
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="currentColor"
|
||||||
|
class="size-6 text-gray-500 shrink-0"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<path d="M12 2c5.523 0 10 4.477 10 10a10 10 0 0 1 -19.995 .324l-.005 -.324l.004 -.28c.148 -5.393 4.566 -9.72 9.996 -9.72zm0 13a1 1 0 0 0 -.993 .883l-.007 .117l.007 .127a1 1 0 0 0 1.986 0l.007 -.117l-.007 -.127a1 1 0 0 0 -.993 -.883zm1.368 -6.673a2.98 2.98 0 0 0 -3.631 .728a1 1 0 0 0 1.44 1.383l.171 -.18a.98 .98 0 0 1 1.11 -.15a1 1 0 0 1 -.34 1.886l-.232 .012a1 1 0 0 0 .111 1.994a3 3 0 0 0 1.371 -5.673z" />
|
||||||
|
</svg>
|
||||||
|
{% endif %}
|
||||||
|
</dt>
|
||||||
<dd class="text-black dark:text-white tracking-tight leading-10 font-medium text-3xl flex-none w-full">
|
<dd class="text-black dark:text-white tracking-tight leading-10 font-medium text-3xl flex-none w-full">
|
||||||
{{ metric.value }}
|
{{ metric.value }}
|
||||||
</dd>
|
</dd>
|
||||||
@@ -130,6 +195,25 @@
|
|||||||
</dl>
|
</dl>
|
||||||
</div>
|
</div>
|
||||||
<div class="shadow-sm bg-white dark:bg-gray-900 rounded-md my-8">
|
<div class="shadow-sm bg-white dark:bg-gray-900 rounded-md my-8">
|
||||||
|
<div class="flex justify-between items-center px-6 py-4 text-gray-500">
|
||||||
|
<h3>Last checked: {{ last_updated }}</h3>
|
||||||
|
<button class="group" onclick="refresh(event)">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
class="group-disabled:animate-spin"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4,11A8.1,8.1 0 0 1 19.5,9M20,5v4h-4" /><path d="M20,13A8.1,8.1 0 0 1 4.5,15M4,19v-4h4" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<ul class="*:py-4 *:px-6 *:flex *:items-center *:gap-3 dark:divide-gray-800 divide-y dark:text-white">
|
<ul class="*:py-4 *:px-6 *:flex *:items-center *:gap-3 dark:divide-gray-800 divide-y dark:text-white">
|
||||||
{% for image in images %}
|
{% for image in images %}
|
||||||
<li>
|
<li>
|
||||||
|
|||||||
26
src/utils.rs
26
src/utils.rs
@@ -1,6 +1,7 @@
|
|||||||
use regex::Regex;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
/// 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]
|
||||||
macro_rules! error {
|
macro_rules! error {
|
||||||
($($arg:tt)*) => ({
|
($($arg:tt)*) => ({
|
||||||
@@ -9,7 +10,7 @@ macro_rules! error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Takes an image and splits it into registry, repository and tag. For example ghcr.io/sergi0g/cup:latest becomes ['ghcr.io', 'sergi0g/cup', 'latest'].
|
/// 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) {
|
pub fn split_image(image: &str) -> (String, String, String) {
|
||||||
static RE: Lazy<Regex> = Lazy::new(|| {
|
static RE: Lazy<Regex> = Lazy::new(|| {
|
||||||
Regex::new(
|
Regex::new(
|
||||||
@@ -45,26 +46,39 @@ 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 {
|
pub fn unsplit_image(registry: &str, repository: &str, tag: &str) -> String {
|
||||||
let reg = match registry {
|
let reg = match registry {
|
||||||
"registry-1.docker.io" => String::new(),
|
"registry-1.docker.io" => String::new(),
|
||||||
r => format!("{}/", r),
|
r => format!("{}/", r),
|
||||||
};
|
};
|
||||||
let repo = match repository.split('/').collect::<Vec<&str>>()[0] {
|
let repo = match repository.split('/').collect::<Vec<&str>>()[0] {
|
||||||
"library" => repository.strip_prefix("library/").unwrap(),
|
"library" => {
|
||||||
|
if reg.is_empty() {
|
||||||
|
repository.strip_prefix("library/").unwrap()
|
||||||
|
} else {
|
||||||
|
repository
|
||||||
|
}
|
||||||
|
}
|
||||||
_ => repository,
|
_ => repository,
|
||||||
};
|
};
|
||||||
format!("{}{}:{}", reg, repo, tag)
|
format!("{}{}:{}", reg, repo, tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "cli")]
|
/// Sorts the update vector alphabetically and where Some(true) > Some(false) > None
|
||||||
pub fn sort_update_vec(updates: &[(String, Option<bool>)]) -> Vec<(String, Option<bool>)> {
|
pub fn sort_update_vec(updates: &[(String, Option<bool>)]) -> Vec<(String, Option<bool>)> {
|
||||||
let mut sorted_updates = updates.to_vec();
|
let mut sorted_updates = updates.to_vec();
|
||||||
sorted_updates.sort_unstable_by(|a, b| match (a.1, b.1) {
|
sorted_updates.sort_unstable_by(|a, b| match (a.1, b.1) {
|
||||||
(Some(a), Some(b)) => (!a).cmp(&!b),
|
(Some(c), Some(d)) => {
|
||||||
|
if c == d {
|
||||||
|
a.0.cmp(&b.0)
|
||||||
|
} else {
|
||||||
|
(!c).cmp(&!d)
|
||||||
|
}
|
||||||
|
}
|
||||||
(Some(_), None) => std::cmp::Ordering::Less,
|
(Some(_), None) => std::cmp::Ordering::Less,
|
||||||
(None, Some(_)) => std::cmp::Ordering::Greater,
|
(None, Some(_)) => std::cmp::Ordering::Greater,
|
||||||
(None, None) => std::cmp::Ordering::Equal,
|
(None, None) => a.0.cmp(&b.0),
|
||||||
});
|
});
|
||||||
sorted_updates.to_vec()
|
sorted_updates.to_vec()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user