mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-17 17:43:37 -05:00
Compare commits
9 Commits
8a5b0555f7
...
v3.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6dc1030a3b | ||
|
|
d2c1651761 | ||
|
|
8b3cf73f65 | ||
|
|
6d88036914 | ||
|
|
a06266264d | ||
|
|
c260874459 | ||
|
|
3e42ac338a | ||
|
|
15784eb4f1 | ||
|
|
2623f52a20 |
85
Cargo.lock
generated
85
Cargo.lock
generated
@@ -260,6 +260,27 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efdce149c370f133a071ca8ef6ea340b7b88748ab0810097a9e2976eaa34b4f3"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"chrono-tz-build",
|
||||
"phf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono-tz-build"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f10f8c9340e31fc120ff885fcdb54a0b48e474bbd77cab557f0c30a3e569402"
|
||||
dependencies = [
|
||||
"parse-zoneinfo",
|
||||
"phf_codegen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "clap"
|
||||
version = "4.5.31"
|
||||
@@ -359,7 +380,9 @@ version = "3.3.0"
|
||||
dependencies = [
|
||||
"bollard",
|
||||
"chrono",
|
||||
"chrono-tz",
|
||||
"clap",
|
||||
"envy",
|
||||
"futures",
|
||||
"http-auth",
|
||||
"http-link",
|
||||
@@ -423,6 +446,15 @@ version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
|
||||
|
||||
[[package]]
|
||||
name = "envy"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
version = "1.0.2"
|
||||
@@ -1192,6 +1224,15 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parse-zoneinfo"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f2a05b18d44e2957b88f96ba460715e295bc1d7510468a2f3d3b44535d26c24"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
@@ -1243,6 +1284,44 @@ dependencies = [
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_codegen"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||
dependencies = [
|
||||
"phf_shared",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.16"
|
||||
@@ -1688,6 +1767,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cup"
|
||||
version = "3.3.0"
|
||||
version = "3.4.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
@@ -25,6 +25,8 @@ itertools = "0.14.0"
|
||||
serde_json = "1.0.133"
|
||||
serde = "1.0.215"
|
||||
tokio-cron-scheduler = { version = "0.13.0", default-features = false, optional = true }
|
||||
envy = "0.4.2"
|
||||
chrono-tz = "0.10.3"
|
||||
|
||||
[features]
|
||||
default = ["server", "cli"]
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Callout } from "nextra/components";
|
||||
|
||||
# Automatic refresh
|
||||
|
||||
Cup can automatically refresh the results when running in server mode. Simply add this to your config:
|
||||
@@ -9,4 +11,8 @@ Cup can automatically refresh the results when running in server mode. Simply ad
|
||||
}
|
||||
```
|
||||
|
||||
You can use a cron expression to specify the refresh interval. Note that seconds are not optional. The reference is [here](https://github.com/Hexagon/croner-rust#pattern)
|
||||
You can use a cron expression to specify the refresh interval. Note that seconds are not optional. The reference is [here](https://github.com/Hexagon/croner-rust#pattern).
|
||||
|
||||
<Callout>
|
||||
If you use a schedule with absolute time (e.g. every day at 6 AM), make sure to set the `TZ` environment variable to your [timezone](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones#List).
|
||||
</Callout>
|
||||
|
||||
@@ -109,3 +109,36 @@ $ docker run -tv /var/run/docker.sock:/var/run/docker.sock -v /home/sergio/.conf
|
||||
```
|
||||
|
||||
</Steps>
|
||||
|
||||
## Environment Variables
|
||||
|
||||
Want to make a quick change without editing your `config.json`? Cup also supports some configuration options from environment variables.
|
||||
Here are the ones currently available:
|
||||
- `CUP_AGENT` - Agent mode
|
||||
- `CUP_IGNORE_UPDATE_TYPE` - Ignoring specific update types
|
||||
- `CUP_REFRESH_INTERVAL` - Automatic refresh
|
||||
- `CUP_SOCKET` - Socket
|
||||
- `CUP_THEME` - Theme
|
||||
|
||||
Refer to the configuration page for more information on each of these.
|
||||
|
||||
Here's an example of a Docker Compose file using them:
|
||||
```yaml
|
||||
services:
|
||||
cup:
|
||||
image: ghcr.io/sergi0g/cup:latest
|
||||
command: serve
|
||||
ports:
|
||||
- 8000:8000
|
||||
environment:
|
||||
- CUP_AGENT: true
|
||||
- CUP_IGNORE_UPDATE_TYPE: major
|
||||
- CUP_REFRESH_INTERVAL: "0 */30 * * * *"
|
||||
- CUP_SOCKET: tcp://localhost:2375
|
||||
- CUP_THEME: blue
|
||||
```
|
||||
|
||||
<Callout>
|
||||
Heads up!
|
||||
Any configuration option you set with environment variables **always** overrides anything in your `cup.json`.
|
||||
</Callout>
|
||||
12
src/check.rs
12
src/check.rs
@@ -90,7 +90,9 @@ pub async fn get_updates(
|
||||
// Merge references argument with references from config
|
||||
let all_references = match &references {
|
||||
Some(refs) => {
|
||||
refs.clone().extend_from_slice(&ctx.config.images.extra);
|
||||
if !ctx.config.images.extra.is_empty() {
|
||||
refs.clone().extend_from_slice(&ctx.config.images.extra);
|
||||
}
|
||||
refs
|
||||
}
|
||||
None => &ctx.config.images.extra,
|
||||
@@ -100,7 +102,8 @@ pub async fn get_updates(
|
||||
ctx.logger.debug("Retrieving images to be checked");
|
||||
let mut images = get_images_from_docker_daemon(ctx, references).await;
|
||||
let in_use_images = get_in_use_images(ctx).await;
|
||||
ctx.logger.debug(format!("Found {} images in use", in_use_images.len()));
|
||||
ctx.logger
|
||||
.debug(format!("Found {} images in use", in_use_images.len()));
|
||||
|
||||
// Complete in_use field
|
||||
images.iter_mut().for_each(|image| {
|
||||
@@ -204,10 +207,7 @@ pub async fn get_updates(
|
||||
}
|
||||
// Await all the futures
|
||||
let images = join_all(handles).await;
|
||||
let mut updates: Vec<Update> = images
|
||||
.iter()
|
||||
.map(|image| image.to_update())
|
||||
.collect();
|
||||
let mut updates: Vec<Update> = images.iter().map(|image| image.to_update()).collect();
|
||||
updates.extend_from_slice(&remote_updates);
|
||||
updates
|
||||
}
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde::Deserialize;
|
||||
use std::env;
|
||||
use std::mem;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::error;
|
||||
|
||||
// We can't assign `a` to `b` in the loop in `Config::load`, so we'll have to use swap. It looks ugly so now we have a macro for it.
|
||||
macro_rules! swap {
|
||||
($a:expr, $b:expr) => {
|
||||
mem::swap(&mut $a, &mut $b)
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Theme {
|
||||
@@ -78,8 +86,41 @@ impl Config {
|
||||
}
|
||||
}
|
||||
|
||||
/// Loads file and env config and merges them
|
||||
pub fn load(&mut self, path: Option<PathBuf>) -> Self {
|
||||
let mut config = self.load_file(path);
|
||||
|
||||
// Get environment variables with CUP_ prefix
|
||||
let env_vars: FxHashMap<String, String> =
|
||||
env::vars().filter(|(k, _)| k.starts_with("CUP_")).collect();
|
||||
|
||||
if !env_vars.is_empty() {
|
||||
if let Ok(mut cfg) = envy::prefixed("CUP_").from_env::<Config>() {
|
||||
// If we have environment variables, override config.json options
|
||||
for (key, _) in env_vars {
|
||||
match key.as_str() {
|
||||
"CUP_AGENT" => config.agent = cfg.agent,
|
||||
#[rustfmt::skip]
|
||||
"CUP_IGNORE_UPDATE_TYPE" => swap!(config.ignore_update_type, cfg.ignore_update_type),
|
||||
#[rustfmt::skip]
|
||||
"CUP_REFRESH_INTERVAL" => swap!(config.refresh_interval, cfg.refresh_interval),
|
||||
"CUP_SOCKET" => swap!(config.socket, cfg.socket),
|
||||
"CUP_THEME" => swap!(config.theme, cfg.theme),
|
||||
// The syntax for these is slightly more complicated, not sure if they should be enabled or not. Let's stick to simple types for now.
|
||||
// "CUP_IMAGES" => swap!(config.images, cfg.images),
|
||||
// "CUP_REGISTRIES" => swap!(config.registries, cfg.registries),
|
||||
// "CUP_SERVERS" => swap!(config.servers, cfg.servers),
|
||||
_ => (), // Maybe print a warning if other CUP_ variables are detected
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
|
||||
/// Reads the config from the file path provided and returns the parsed result.
|
||||
pub fn load(&self, path: Option<PathBuf>) -> Self {
|
||||
fn load_file(&self, path: Option<PathBuf>) -> Self {
|
||||
let raw_config = match &path {
|
||||
Some(path) => std::fs::read_to_string(path),
|
||||
None => return Self::new(), // Empty config
|
||||
@@ -93,7 +134,7 @@ impl Config {
|
||||
self.parse(&raw_config.unwrap()) // We can safely unwrap here
|
||||
}
|
||||
/// Parses and validates the config.
|
||||
pub fn parse(&self, raw_config: &str) -> Self {
|
||||
fn parse(&self, raw_config: &str) -> Self {
|
||||
let config: Self = match serde_json::from_str(raw_config) {
|
||||
Ok(config) => config,
|
||||
Err(e) => error!("Unexpected error occured while parsing config: {}", e),
|
||||
|
||||
@@ -69,6 +69,10 @@ impl Client {
|
||||
self.ctx.logger.warn(&message);
|
||||
Err(message)
|
||||
}
|
||||
} else if status == 502 {
|
||||
let message = format!("{} {}: The registry is currently unavailabile (returned status code 502).", method, url);
|
||||
self.ctx.logger.warn(&message);
|
||||
Err(message)
|
||||
} else if status.as_u16() <= 400 {
|
||||
Ok(response)
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::Arc;
|
||||
use std::{env, sync::Arc};
|
||||
|
||||
use chrono::Local;
|
||||
use chrono_tz::Tz;
|
||||
use liquid::{object, Object, ValueView};
|
||||
use rustc_hash::FxHashMap;
|
||||
use serde_json::Value;
|
||||
@@ -54,15 +55,22 @@ pub async fn serve(port: &u16, ctx: &Context) -> std::io::Result<()> {
|
||||
let scheduler = JobScheduler::new().await.unwrap();
|
||||
let data = Arc::new(Mutex::new(data));
|
||||
let data_copy = data.clone();
|
||||
let tz = env::var("TZ")
|
||||
.map(|tz| tz.parse().unwrap_or(Tz::UTC))
|
||||
.unwrap_or(Tz::UTC);
|
||||
if let Some(interval) = &ctx.config.refresh_interval {
|
||||
scheduler
|
||||
.add(
|
||||
match Job::new_async(interval, move |_uuid, _lock| {
|
||||
let data_copy = data_copy.clone();
|
||||
Box::pin(async move {
|
||||
data_copy.lock().await.refresh().await;
|
||||
})
|
||||
}) {
|
||||
match Job::new_async_tz(
|
||||
interval,
|
||||
tz,
|
||||
move |_uuid, _lock| {
|
||||
let data_copy = data_copy.clone();
|
||||
Box::pin(async move {
|
||||
data_copy.lock().await.refresh().await;
|
||||
})
|
||||
},
|
||||
) {
|
||||
Ok(job) => job,
|
||||
Err(e) => match e {
|
||||
tokio_cron_scheduler::JobSchedulerError::ParseSchedule => error!(
|
||||
|
||||
Reference in New Issue
Block a user