m/cup
1
0
mirror of https://github.com/sergi0g/cup.git synced 2025-11-12 23:23:48 -05:00

9 Commits

Author SHA1 Message Date
Sergio
e26f941c59 chore: bump project version 2025-03-16 18:42:06 +02:00
Sergio
c411fc4bad fix: ignore invalid digests instead of panicking 2025-03-16 18:40:10 +02:00
Sergio
e965380133 fix: improve error handling in get_latest_tag when an image has no tags
This commit is mostly for debugging #68, but it's good to have more
error info just in case.
2025-03-16 18:33:19 +02:00
Sergio
ef849b624f fix: don't pass empty parameters when making auth request (#69) 2025-03-16 18:26:04 +02:00
Sergio
8db7e2e12b fix: improve error handling when scheduling automatic refresh 2025-03-15 12:28:39 +02:00
Sergio
54e1998032 docs: fix incorrect cron schedule example 2025-03-15 12:28:39 +02:00
Sergio
9f142ab81c fix: add error message when app fails to bind to port 2025-03-15 12:28:39 +02:00
Sergio
ffd4d6267c chore: update readme
forgot to add a link
2025-03-14 10:20:51 +02:00
Sergio
242029db22 chore: update readme 2025-03-14 10:15:38 +02:00
9 changed files with 48 additions and 19 deletions

2
Cargo.lock generated
View File

@@ -355,7 +355,7 @@ dependencies = [
[[package]]
name = "cup"
version = "3.2.0-alpha.1"
version = "3.2.0"
dependencies = [
"bollard",
"chrono",

View File

@@ -1,6 +1,6 @@
[package]
name = "cup"
version = "3.2.0-alpha.1"
version = "3.2.0"
edition = "2021"
[dependencies]

View File

@@ -23,7 +23,7 @@ _If you like this project and/or use Cup, please consider starring the project
- Extremely fast. Cup takes full advantage of your CPU and is hightly optimized, resulting in lightning fast speed. On my Raspberry Pi 5, it took 3.7 seconds for 58 images!
- Supports most registries, including Docker Hub, ghcr.io, Quay, lscr.io and even Gitea (or derivatives)
- Doesn't exhaust any rate limits. This is the original reason I created Cup. It was inspired by [What's up docker?](https://github.com/getwud/wud) which would always use it up.
- Doesn't exhaust any rate limits. This is the original reason I created Cup. I feel that this feature is especially relevant now with [Docker Hub reducing its pull limits for unauthenticated users](https://docs.docker.com/docker-hub/usage/).
- Beautiful CLI and web interface for checking on your containers any time.
- The binary is tiny! At the time of writing it's just 5.4 MB. No more pulling 100+ MB docker images for a such a simple program.
- JSON output for both the CLI and web interface so you can connect Cup to integrations. It's easy to parse and makes webhooks and pretty dashboards simple to set up!
@@ -56,7 +56,7 @@ For more information, check the [docs](https://cup.sergi0g.dev/docs/contributing
## Support
If you have any questions about Cup, feel free to ask in the [discussions](https://github.com/sergi0g/cup/discussions)!
If you have any questions about Cup, feel free to ask in the [discussions](https://github.com/sergi0g/cup/discussions)! You can also join our [discord server](https://discord.gg/jmh5ctzwNG).
If you find a bug, or want to propose a feature, search for it in the [issues](https://github.com/sergi0g/cup/issues). If there isn't already an open issue, please open one.

View File

@@ -4,7 +4,7 @@ Cup can automatically refresh the results when running in server mode. Simply ad
```jsonc
{
"refresh_interval": "0 0,30 * 0 0" // Check twice an hour
"refresh_interval": "0 0,30 * * * *" // Check twice an hour
// Other options
}
```

View File

@@ -49,7 +49,7 @@ pub async fn get_images_from_docker_daemon(
Some(service_spec) => match &service_spec.task_template {
Some(task_spec) => match &task_spec.container_spec {
Some(container_spec) => match &container_spec.image {
Some(image) => Image::from_inspect_data(image),
Some(image) => Image::from_inspect_data(ctx, image),
None => None,
},
None => None,
@@ -75,7 +75,7 @@ pub async fn get_images_from_docker_daemon(
.collect();
inspects
.iter()
.filter_map(|inspect| Image::from_inspect_data(inspect.clone()))
.filter_map(|inspect| Image::from_inspect_data(ctx, inspect.clone()))
.collect()
}
None => {
@@ -87,7 +87,7 @@ pub async fn get_images_from_docker_daemon(
};
images
.iter()
.filter_map(|image| Image::from_inspect_data(image.clone()))
.filter_map(|image| Image::from_inspect_data(ctx, image.clone()))
.collect::<Vec<Image>>()
}
};

View File

@@ -205,7 +205,7 @@ pub async fn get_latest_tag(
}
}
}
None => unreachable!("{:?}", tags),
None => error!("Image {} has no remote version tags! Local tag: {}", image.reference, image.parts.tag),
}
}

View File

@@ -19,6 +19,7 @@ use xitca_web::{
use crate::{
check::get_updates,
config::Theme,
error,
structs::update::Update,
utils::{
json::{to_full_json, to_simple_json},
@@ -55,13 +56,24 @@ pub async fn serve(port: &u16, ctx: &Context) -> std::io::Result<()> {
if let Some(interval) = &ctx.config.refresh_interval {
scheduler
.add(
Job::new_async(interval, move |_uuid, _lock| {
match Job::new_async(interval, move |_uuid, _lock| {
let data_copy = data_copy.clone();
Box::pin(async move {
data_copy.lock().await.refresh().await;
})
})
.unwrap(),
}) {
Ok(job) => job,
Err(e) => match e {
tokio_cron_scheduler::JobSchedulerError::ParseSchedule => error!(
"Failed to parse cron schedule: {}. Please ensure it is valid!",
interval
),
e => error!(
"An unexpected error occured while scheduling automatic refresh: {}",
e
),
},
},
)
.await
.unwrap();
@@ -79,12 +91,16 @@ pub async fn serve(port: &u16, ctx: &Context) -> std::io::Result<()> {
.at("/", get(handler_service(_static)))
.at("/*", get(handler_service(_static)));
}
app_builder
match app_builder
.enclosed_fn(logger)
.serve()
.bind(format!("0.0.0.0:{}", port))?
.run()
.wait()
.bind(format!("0.0.0.0:{}", port))
{
Ok(r) => r,
Err(_) => error!("Failed to bind to port {}. Is it in use?", port),
}
.run()
.wait()
}
async fn _static(data: StateRef<'_, Arc<Mutex<ServerData>>>, path: PathRef<'_>) -> WebResponse {

View File

@@ -44,7 +44,7 @@ pub struct Image {
impl Image {
/// Creates and populates the fields of an Image object based on the ImageSummary from the Docker daemon
pub fn from_inspect_data<T: InspectData>(image: T) -> Option<Self> {
pub fn from_inspect_data<T: InspectData>(ctx: &Context, image: T) -> Option<Self> {
let tags = image.tags().unwrap();
let digests = image.digests().unwrap();
if !tags.is_empty() && !digests.is_empty() {
@@ -56,7 +56,18 @@ impl Image {
let version_tag = Version::from_tag(&tag);
let local_digests = digests
.iter()
.map(|digest| digest.split('@').collect::<Vec<&str>>()[1].to_string())
.filter_map(
|digest| match digest.split('@').collect::<Vec<&str>>().get(1) {
Some(digest) => Some(digest.to_string()),
None => {
ctx.logger.warn(format!(
"Ignoring invalid digest {} for image {}!",
digest, reference
));
None
}
},
)
.collect();
Some(Self {
reference,

View File

@@ -17,8 +17,10 @@ pub fn parse_www_authenticate(www_auth: &str) -> String {
.fold(String::new(), |acc, (key, value)| {
if *key == "realm" {
acc.to_owned() + value.as_escaped() + "?"
} else {
} else if value.unescaped_len() != 0 {
format!("{}&{}={}", acc, key, value.as_escaped())
} else {
acc
}
})
} else {