mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-08 13:13:49 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7673c04db | ||
|
|
7292ed3d1b | ||
|
|
def2efa0d1 | ||
|
|
21c110011f | ||
|
|
c969ded188 | ||
|
|
53f32958fc | ||
|
|
82ec9b6e52 | ||
|
|
8ad5cbb127 | ||
|
|
7ea4c63322 | ||
|
|
30b8e943c0 | ||
|
|
0f7245dbf4 | ||
|
|
2549ed7801 | ||
|
|
90239f83e9 | ||
|
|
dc7a981930 | ||
|
|
8d2740dc7d | ||
|
|
fb674acf96 | ||
|
|
e9160334d9 | ||
|
|
ca6ffea29c | ||
|
|
923e81d75d |
34
.github/workflows/build.yml
vendored
34
.github/workflows/build.yml
vendored
@@ -18,21 +18,10 @@ jobs:
|
||||
build-binary:
|
||||
strategy:
|
||||
matrix:
|
||||
platform:
|
||||
- release_for: linux-aarch64
|
||||
os: ubuntu-latest
|
||||
target: aarch64-unknown-linux-musl
|
||||
bin: cup
|
||||
name: cup-linux-aarch64
|
||||
command: build
|
||||
|
||||
- release_for: linux-x86_64
|
||||
os: ubuntu-latest
|
||||
target: x86_64-unknown-linux-musl
|
||||
bin: cup
|
||||
name: cup-linux-x86_64
|
||||
command: build
|
||||
runs-on: ${{ matrix.platform.os }}
|
||||
arch:
|
||||
- aarch64
|
||||
- x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@@ -44,13 +33,13 @@ jobs:
|
||||
run: cargo install cross --git https://github.com/cross-rs/cross
|
||||
|
||||
- name: Build binary
|
||||
run: cross ${{ matrix.platform.command }} --target ${{ matrix.platform.target }} --release
|
||||
run: cross build --target ${{ matrix.arch }}-unknown-linux-musl --release
|
||||
|
||||
- name: Upload binary
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ matrix.platform.name }}
|
||||
path: target/${{ matrix.platform.target }}/release/${{ matrix.platform.bin }}
|
||||
name: cup-linux-${{ matrix.arch }}
|
||||
path: target/${{ matrix.arch }}-unknown-linux-musl/release/cup
|
||||
|
||||
build-image:
|
||||
needs: get-tag
|
||||
@@ -98,6 +87,11 @@ jobs:
|
||||
name: cup-linux-x86_64
|
||||
path: cup-linux-x86_64
|
||||
|
||||
# - name: Extract and rename binaries
|
||||
# run: |
|
||||
# unzip /home/runner/work/cup/cup/cup-linux-aarch64 && mv /home/runner/work/cup/cup/cup cup-linux-aarch64
|
||||
# unzip /home/runner/work/cup/cup/cup-linux-x86_64 && mv /home/runner/work/cup/cup/cup cup-linux-x86_64
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
env:
|
||||
@@ -107,5 +101,5 @@ jobs:
|
||||
tag_name: ${{ needs.get-tag.outputs.tag }}
|
||||
name: ${{ needs.get-tag.outputs.tag }}
|
||||
files: |
|
||||
cup-linux-aarch64/cup-linux-aarch64
|
||||
cup-linux-x86_64/cup-linux-x86_64
|
||||
cup-linux-aarch64
|
||||
cup-linux-x86_64
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
||||
/docs/.next
|
||||
/docs/node_modules
|
||||
/docs/out
|
||||
|
||||
# In case I accidentally commit mine...
|
||||
cup.json
|
||||
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -350,7 +350,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cup"
|
||||
version = "1.1.3"
|
||||
version = "2.1.0"
|
||||
dependencies = [
|
||||
"bollard",
|
||||
"chrono",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cup"
|
||||
version = "1.1.3"
|
||||
version = "2.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@@ -20,14 +20,14 @@ Cup is the easiest way to check for container image updates.
|
||||
|
||||
## Documentation
|
||||
|
||||
Take a look at https://sergi0g.github.io/cup/docs/introduction!
|
||||
Take a look at https://sergi0g.github.io/cup/docs!
|
||||
|
||||
## Limitations
|
||||
|
||||
Cup is a work in progress. It might not have as many features as What's up Docker. If one of these features is really important for you, please consider using another tool.
|
||||
|
||||
- ~~Cup currently doesn't support registries which use repositories without slashes. This includes Azure. This problem may sound a bit weird, but it's due to the regex that's used at the moment. This will (hopefully) be fixed in the future.~~
|
||||
- Cup doesn't support private images. This is on the roadmap. Currently, it just returns unknown for those images.
|
||||
- ~~Cup doesn't support private images. This is on the roadmap. Currently, it just returns unknown for those images.~~
|
||||
- Cup cannot trigger your integrations. If you want that to happen automatically, please use What's up docker instead. Cup was created to be simple. The data is there, and it's up to you to retrieve it (e.g. by running `cup check -r` with a cronjob or periodically requesting the `/json` url from the server)
|
||||
|
||||
## Roadmap
|
||||
|
||||
1
docs/.tool-versions
Normal file
1
docs/.tool-versions
Normal file
@@ -0,0 +1 @@
|
||||
nodejs 21.6.2
|
||||
@@ -2,6 +2,15 @@
|
||||
"index": {
|
||||
"title": "Introduction"
|
||||
},
|
||||
"installation": {
|
||||
"title": "Installation"
|
||||
},
|
||||
"configuration": {
|
||||
"title": "Configuration"
|
||||
},
|
||||
"usage": {
|
||||
"title": "Usage"
|
||||
},
|
||||
"nightly": {
|
||||
"title": "Using the latest version"
|
||||
}
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import Image from "next/image";
|
||||
import { Steps, Callout } from "nextra-theme-docs";
|
||||
import blue from "../../assets/blue_theme.png"
|
||||
import gray from "../../assets/gray_theme.png"
|
||||
import { Steps, Callout, Card, Cards } from "nextra-theme-docs";
|
||||
import { IconPaint, IconLockOpen, IconKey } from '@tabler/icons-react';
|
||||
|
||||
# Configuration
|
||||
|
||||
@@ -15,6 +13,8 @@ For example, if using Podman, you might do
|
||||
$ cup -s /run/user/1000/podman/podman.sock check
|
||||
```
|
||||
|
||||
This option will hopefully be moved to the configuration file soon.
|
||||
|
||||
## Configuration file
|
||||
|
||||
Cup has an option to be configured from a configuration file named `cup.json`.
|
||||
@@ -25,16 +25,23 @@ Create a `cup.json` file somewhere on your system. For binary installs, a path l
|
||||
If you're running with Docker, you can create a `cup.json` in the directory you're running cup and mount it into the container. _In the next section you will need to use the path where you **mounted** the file_
|
||||
|
||||
### Configure Cup from the configuration file
|
||||
Follow the guides below (Theme and Authentication) to make your `cup.json`
|
||||
Follow the guides below to customize your `cup.json`
|
||||
|
||||
<Cards>
|
||||
<Card icon={<IconKey />} title="Authentication" href="/docs/configuration/authentication" />
|
||||
<Card icon={<IconLockOpen />} title="Insecure registries" href="/docs/configuration/insecure-registries" />
|
||||
<Card icon={<IconPaint />} title="Theme" href="/docs/configuration/theme" />
|
||||
</Cards>
|
||||
|
||||
Here's a full example:
|
||||
```json
|
||||
{
|
||||
authentication: {
|
||||
"authentication": {
|
||||
"ghcr.io": "<YOUR_TOKEN_HERE>",
|
||||
"registry-1.docker.io": "<YOUR_TOKEN_HERE>"
|
||||
},
|
||||
theme: "blue"
|
||||
"theme": "blue",
|
||||
"insecure_registries": ["localhost:5000", "my-insecure-registry.example.com"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -49,50 +56,3 @@ $ cup -c /home/sergio/.config/cup.json check
|
||||
$ docker run -tv /var/run/docker.sock:/var/run/docker.sock -v /home/sergio/.config/cup.json:/config/cup.json ghcr.io/sergi0g/cup -c /config/cup.json serve
|
||||
```
|
||||
</Steps>
|
||||
|
||||
## Theme (server only)
|
||||
|
||||
Cup initially had a blue theme which looked like this:
|
||||
|
||||
<Image alt="Screenshot of blue theme" src={blue} />
|
||||
|
||||
This was replaced by a more neutral theme which is now the default:
|
||||
|
||||
<Image alt="Screenshot of neutral theme" src={gray} />
|
||||
|
||||
However, you can get the old theme back by adding the `theme` key to your `cup.json`
|
||||
Available values are `default` and `blue`.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": "blue",
|
||||
// Other options
|
||||
}
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
<Callout emoji="⛔">
|
||||
The features described in this section have not been implemented yet.
|
||||
</Callout>
|
||||
|
||||
Some registries (or specific images) may require you to be authenticated. For those, you can modify `cup.json` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"authentication": {
|
||||
"<YOUR_REGISTRY_DOMAIN_1>": "<YOUR_TOKEN_1>",
|
||||
"<YOUR_REGISTRY_DOMAIN_2>": "<YOUR_TOKEN_2>"
|
||||
// ...
|
||||
},
|
||||
// Other options
|
||||
}
|
||||
```
|
||||
|
||||
You can use any registry, like `ghcr.io`, `quay.io`, `gcr.io`, etc.
|
||||
|
||||
<Callout emoji="⚠️">
|
||||
For Docker Hub, use `registry-1.docker.io`
|
||||
</Callout>
|
||||
22
docs/pages/docs/configuration/authentication.mdx
Normal file
22
docs/pages/docs/configuration/authentication.mdx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Callout } from 'nextra-theme-docs'
|
||||
|
||||
# Authentication
|
||||
|
||||
Some registries (or specific images) may require you to be authenticated. For those, you can modify `cup.json` like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"authentication": {
|
||||
"<YOUR_REGISTRY_DOMAIN_1>": "<YOUR_TOKEN_1>",
|
||||
"<YOUR_REGISTRY_DOMAIN_2>": "<YOUR_TOKEN_2>"
|
||||
// ...
|
||||
},
|
||||
// Other options
|
||||
}
|
||||
```
|
||||
|
||||
You can use any registry, like `ghcr.io`, `quay.io`, `gcr.io`, etc.
|
||||
|
||||
<Callout emoji="⚠️">
|
||||
For Docker Hub, use `registry-1.docker.io`
|
||||
</Callout>
|
||||
20
docs/pages/docs/configuration/insecure-registries.mdx
Normal file
20
docs/pages/docs/configuration/insecure-registries.mdx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { Callout } from 'nextra-theme-docs'
|
||||
|
||||
# Insecure registries
|
||||
|
||||
For the best security, Cup only connects to registries over SSL (HTTPS) by default. However, for people running a local registry that haven't configured SSL, this may be a problem.
|
||||
|
||||
To solve this problem, `cup.json` has an `"insecure_registries"` option which allows you to specify exceptions
|
||||
|
||||
Here's what it looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"insecure_registries": ["<INSECURE_REGISTRY_1>", "<INSECURE_REGISTRY_2>"],
|
||||
// Other options
|
||||
}
|
||||
```
|
||||
|
||||
<Callout emoji="⚠️">
|
||||
When configuring an insecure registry that doesn't run on port 80, don't forget to specify it (i.e. use `localhost:5000` instead of `localhost` if your registry is running on port `5000`)
|
||||
</Callout>
|
||||
31
docs/pages/docs/configuration/theme.mdx
Normal file
31
docs/pages/docs/configuration/theme.mdx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Callout } from "nextra-theme-docs";
|
||||
import Image from "next/image";
|
||||
|
||||
import blue from "../../../assets/blue_theme.png";
|
||||
import gray from "../../../assets/gray_theme.png";
|
||||
|
||||
# Theme
|
||||
|
||||
<Callout emoji="⚠️">
|
||||
This configuration option is only for the server
|
||||
</Callout>
|
||||
|
||||
Cup initially had a blue theme which looked like this:
|
||||
|
||||
<Image alt="Screenshot of blue theme" src={blue} />
|
||||
|
||||
This was replaced by a more neutral theme which is now the default:
|
||||
|
||||
<Image alt="Screenshot of neutral theme" src={gray} />
|
||||
|
||||
However, you can get the old theme back by adding the `theme` key to your `cup.json`
|
||||
Available values are `default` and `blue`.
|
||||
|
||||
Here's an example:
|
||||
|
||||
```json
|
||||
{
|
||||
"theme": "blue",
|
||||
// Other options
|
||||
}
|
||||
```
|
||||
24
src/check.rs
24
src/check.rs
@@ -1,8 +1,10 @@
|
||||
use std::{collections::{HashMap, HashSet}, sync::Mutex};
|
||||
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use json::JsonValue;
|
||||
|
||||
use crate::{docker::get_images_from_docker_daemon, image::Image, registry::{check_auth, get_token, get_latest_digests}, utils::unsplit_image};
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
use crate::docker::get_image_from_docker_daemon;
|
||||
#[cfg(feature = "cli")]
|
||||
@@ -23,7 +25,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_all_updates(socket: Option<String>) -> Vec<(String, Option<bool>)> {
|
||||
pub async fn get_all_updates(socket: Option<String>, config: &JsonValue) -> Vec<(String, Option<bool>)> {
|
||||
let image_map_mutex: Mutex<HashMap<String, &Option<String>>> = Mutex::new(HashMap::new());
|
||||
let local_images = get_images_from_docker_daemon(socket).await;
|
||||
local_images.par_iter().for_each(|image| {
|
||||
@@ -42,12 +44,13 @@ pub async fn get_all_updates(socket: Option<String>) -> Vec<(String, Option<bool
|
||||
.par_iter()
|
||||
.filter(|image| &image.registry == registry)
|
||||
.collect();
|
||||
let mut latest_images = match check_auth(registry) {
|
||||
let credentials = config["authentication"][registry].clone().take_string().or(None);
|
||||
let mut latest_images = match check_auth(registry, config) {
|
||||
Some(auth_url) => {
|
||||
let token = get_token(images.clone(), &auth_url);
|
||||
get_latest_digests(images, Some(&token))
|
||||
let token = get_token(images.clone(), &auth_url, &credentials);
|
||||
get_latest_digests(images, Some(&token), config)
|
||||
}
|
||||
None => get_latest_digests(images, None),
|
||||
None => get_latest_digests(images, None, config),
|
||||
};
|
||||
remote_images.append(&mut latest_images);
|
||||
}
|
||||
@@ -67,15 +70,16 @@ pub async fn get_all_updates(socket: Option<String>) -> Vec<(String, Option<bool
|
||||
}
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub async fn get_update(image: &str, socket: Option<String>) -> Option<bool> {
|
||||
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 token = match check_auth(&local_image.registry) {
|
||||
Some(auth_url) => get_token(vec![&local_image], &auth_url),
|
||||
let credentials = config["authentication"][&local_image.registry].clone().take_string().or(None);
|
||||
let token = match check_auth(&local_image.registry, config) {
|
||||
Some(auth_url) => get_token(vec![&local_image], &auth_url, &credentials),
|
||||
None => String::new(),
|
||||
};
|
||||
let remote_image = match token.as_str() {
|
||||
"" => get_latest_digest(&local_image, None),
|
||||
_ => get_latest_digest(&local_image, Some(&token)),
|
||||
"" => get_latest_digest(&local_image, None, config),
|
||||
_ => get_latest_digest(&local_image, Some(&token), config),
|
||||
};
|
||||
match &remote_image.digest {
|
||||
Some(d) => Some(d != &local_image.digest.unwrap()),
|
||||
|
||||
@@ -69,7 +69,7 @@ async fn main() {
|
||||
#[cfg(feature = "cli")]
|
||||
Some(Commands::Check { image, icons, raw }) => match image {
|
||||
Some(name) => {
|
||||
let has_update = get_update(name, cli.socket).await;
|
||||
let has_update = get_update(name, cli.socket, &config).await;
|
||||
match raw {
|
||||
true => print_raw_update(name, &has_update),
|
||||
false => print_update(name, &has_update),
|
||||
@@ -77,10 +77,10 @@ async fn main() {
|
||||
}
|
||||
None => {
|
||||
match raw {
|
||||
true => print_raw_updates(&get_all_updates(cli.socket).await),
|
||||
true => print_raw_updates(&get_all_updates(cli.socket, &config).await),
|
||||
false => {
|
||||
let spinner = Spinner::new();
|
||||
let updates = get_all_updates(cli.socket).await;
|
||||
let updates = get_all_updates(cli.socket, &config).await;
|
||||
spinner.succeed();
|
||||
print_updates(&updates, icons);
|
||||
}
|
||||
|
||||
@@ -2,47 +2,65 @@ use std::sync::Mutex;
|
||||
|
||||
use json::JsonValue;
|
||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||
use ureq::Error;
|
||||
use ureq::{Error, ErrorKind};
|
||||
|
||||
use http_auth::parse_challenges;
|
||||
|
||||
use crate::{error, image::Image};
|
||||
use crate::{error, image::Image, warn};
|
||||
|
||||
pub fn check_auth(registry: &str) -> Option<String> {
|
||||
let response = ureq::get(&format!("https://{}/v2/", registry)).call();
|
||||
pub fn check_auth(registry: &str, config: &JsonValue) -> Option<String> {
|
||||
let protocol = if config["insecure_registries"].contains(registry) { "http" } else { "https" };
|
||||
let response = ureq::get(&format!("{}://{}/v2/", protocol, registry)).call();
|
||||
match response {
|
||||
Ok(_) => None,
|
||||
Err(Error::Status(401, response)) => match response.header("www-authenticate") {
|
||||
Some(challenge) => Some(parse_www_authenticate(challenge)),
|
||||
None => error!("Server returned invalid response!"),
|
||||
None => error!("Unauthorized to access registry {} and no way to authenticate was provided", registry),
|
||||
},
|
||||
Err(Error::Transport(error)) => {
|
||||
match error.kind() {
|
||||
ErrorKind::Dns => {
|
||||
warn!("Failed to lookup the IP of the registry, retrying.");
|
||||
return check_auth(registry, config)
|
||||
}, // If something goes really wrong, this can get stuck in a loop
|
||||
ErrorKind::ConnectionFailed => {
|
||||
warn!("Connection probably timed out, retrying.");
|
||||
return check_auth(registry, config)
|
||||
}, // Same here
|
||||
_ => error!("{}", error)
|
||||
}
|
||||
},
|
||||
Err(e) => error!("{}", e),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_latest_digest(image: &Image, token: Option<&String>) -> Image {
|
||||
pub fn get_latest_digest(image: &Image, token: Option<&String>, config: &JsonValue) -> Image {
|
||||
let protocol = if config["insecure_registries"].contains(json::JsonValue::from(image.registry.clone())) { "http" } else { "https" };
|
||||
let mut request = ureq::head(&format!(
|
||||
"https://{}/v2/{}/manifests/{}",
|
||||
&image.registry, &image.repository, &image.tag
|
||||
"{}://{}/v2/{}/manifests/{}",
|
||||
protocol, &image.registry, &image.repository, &image.tag
|
||||
));
|
||||
if let Some(t) = token {
|
||||
request = request.set("Authorization", &format!("Bearer {}", t));
|
||||
}
|
||||
let raw_response = match request
|
||||
.set("Accept", "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.oci.image.index.v1+json")
|
||||
.set("Accept", "application/vnd.docker.distribution.manifest.list.v2+json, application/vnd.docker.distribution.manifest.v2+json, application/vnd.oci.image.index.v1+json")
|
||||
.call()
|
||||
{
|
||||
Ok(response) => response,
|
||||
Err(Error::Status(401, response)) => {
|
||||
if token.is_some() {
|
||||
error!("Failed to authenticate to registry {} with given token!\n{}", &image.registry, token.unwrap())
|
||||
warn!("Failed to authenticate to registry {} with given token!\n{}", &image.registry, token.unwrap());
|
||||
return Image { digest: None, ..image.clone() }
|
||||
} else {
|
||||
return get_latest_digest(
|
||||
image,
|
||||
Some(&get_token(
|
||||
vec![image],
|
||||
&parse_www_authenticate(response.header("www-authenticate").unwrap()),
|
||||
&None // I think?
|
||||
)),
|
||||
config
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -51,8 +69,20 @@ pub fn get_latest_digest(image: &Image, token: Option<&String>) -> Image {
|
||||
digest: None,
|
||||
..image.clone()
|
||||
}
|
||||
},
|
||||
Err(Error::Transport(error)) => {
|
||||
match error.kind() {
|
||||
ErrorKind::Dns => {
|
||||
warn!("Failed to lookup the IP of the registry, retrying.");
|
||||
return get_latest_digest(image, token, config)
|
||||
}, // If something goes really wrong, this can get stuck in a loop
|
||||
ErrorKind::ConnectionFailed => {
|
||||
warn!("Connection probably timed out, retrying.");
|
||||
return get_latest_digest(image, token, config)
|
||||
}, // Same here
|
||||
_ => error!("Failed to retrieve image digest\n{}!", error)
|
||||
}
|
||||
Err(ureq::Error::Transport(e)) => error!("Failed to send request!\n{}", e),
|
||||
},
|
||||
};
|
||||
match raw_response.header("docker-content-digest") {
|
||||
Some(digest) => Image {
|
||||
@@ -63,10 +93,10 @@ pub fn get_latest_digest(image: &Image, token: Option<&String>) -> Image {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_latest_digests(images: Vec<&Image>, token: Option<&String>) -> Vec<Image> {
|
||||
pub fn get_latest_digests(images: Vec<&Image>, token: Option<&String>, config: &JsonValue) -> Vec<Image> {
|
||||
let result: Mutex<Vec<Image>> = Mutex::new(Vec::new());
|
||||
images.par_iter().for_each(|&image| {
|
||||
let digest = get_latest_digest(image, token).digest;
|
||||
let digest = get_latest_digest(image, token, config).digest;
|
||||
result.lock().unwrap().push(Image {
|
||||
digest,
|
||||
..image.clone()
|
||||
@@ -76,14 +106,17 @@ pub fn get_latest_digests(images: Vec<&Image>, token: Option<&String>) -> Vec<Im
|
||||
r
|
||||
}
|
||||
|
||||
pub fn get_token(images: Vec<&Image>, auth_url: &str) -> String {
|
||||
pub fn get_token(images: Vec<&Image>, auth_url: &str, credentials: &Option<String>) -> String {
|
||||
let mut final_url = auth_url.to_owned();
|
||||
for image in images {
|
||||
for image in &images {
|
||||
final_url = format!("{}&scope=repository:{}:pull", final_url, image.repository);
|
||||
}
|
||||
let raw_response = match ureq::get(&final_url)
|
||||
.set("Accept", "application/vnd.oci.image.index.v1+json")
|
||||
.call()
|
||||
let mut base_request = ureq::get(&final_url).set("Accept", "application/vnd.oci.image.index.v1+json"); // Seems to be unnecesarry. Will probably remove in the future
|
||||
base_request = match credentials {
|
||||
Some(creds) => base_request.set("Authorization", &format!("Basic {}", creds)),
|
||||
None => base_request
|
||||
};
|
||||
let raw_response = match base_request.call()
|
||||
{
|
||||
Ok(response) => match response.into_string() {
|
||||
Ok(res) => res,
|
||||
@@ -91,6 +124,19 @@ pub fn get_token(images: Vec<&Image>, auth_url: &str) -> String {
|
||||
error!("Failed to parse response into string!\n{}", e)
|
||||
}
|
||||
},
|
||||
Err(Error::Transport(error)) => {
|
||||
match error.kind() {
|
||||
ErrorKind::Dns => {
|
||||
warn!("Failed to lookup the IP of the registry, retrying.");
|
||||
return get_token(images, auth_url, credentials)
|
||||
}, // If something goes really wrong, this can get stuck in a loop
|
||||
ErrorKind::ConnectionFailed => {
|
||||
warn!("Connection probably timed out, retrying.");
|
||||
return get_token(images, auth_url, credentials)
|
||||
}, // Same here
|
||||
_ => error!("Token request failed\n{}!", error)
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
error!("Token request failed!\n{}", e)
|
||||
}
|
||||
@@ -118,6 +164,6 @@ fn parse_www_authenticate(www_auth: &str) -> String {
|
||||
error!("Unsupported scheme {}", &challenge.scheme)
|
||||
}
|
||||
} else {
|
||||
error!("No challenge provided");
|
||||
error!("No challenge provided by the server");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ impl ServerData {
|
||||
s
|
||||
}
|
||||
async fn refresh(&mut self) {
|
||||
let updates = sort_update_vec(&get_all_updates(self.socket.clone()).await);
|
||||
let updates = sort_update_vec(&get_all_updates(self.socket.clone(), &self.config["authentication"]).await);
|
||||
self.raw_updates = updates;
|
||||
let template = liquid::ParserBuilder::with_stdlib()
|
||||
.build()
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -144,8 +144,13 @@
|
||||
{% for metric in metrics %}
|
||||
<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">
|
||||
<dt class="text-{{ theme }}-500 dark:text-{{ theme }}-400 leading-6 font-medium flex gap-1 justify-between">
|
||||
<dt class="text-{{ theme }}-500 dark:text-{{ theme }}-400 leading-6 font-medium">
|
||||
{{ metric.name }}
|
||||
</dt>
|
||||
<div class="flex gap-1 justify-between items-center">
|
||||
<dd class="text-black dark:text-white tracking-tight leading-10 font-medium text-3xl w-full">
|
||||
{{ metric.value }}
|
||||
</dd>
|
||||
{% if metric.name == 'Monitored images' %}
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
@@ -194,10 +199,7 @@
|
||||
<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">
|
||||
{{ metric.value }}
|
||||
</dd>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
17
src/utils.rs
17
src/utils.rs
@@ -13,6 +13,14 @@ macro_rules! error {
|
||||
})
|
||||
}
|
||||
|
||||
// A small macro to print in yellow as a warning
|
||||
#[macro_export]
|
||||
macro_rules! warn {
|
||||
($($arg:tt)*) => ({
|
||||
eprintln!("\x1b[93m{}\x1b[0m", format!($($arg)*));
|
||||
})
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
static RE: Lazy<Regex> = Lazy::new(|| {
|
||||
@@ -23,15 +31,16 @@ pub fn split_image(image: &str) -> (String, String, String) {
|
||||
});
|
||||
match RE.captures(image) {
|
||||
Some(c) => {
|
||||
return (
|
||||
match c.name("registry") {
|
||||
let registry = match c.name("registry") {
|
||||
Some(registry) => registry.as_str().to_owned(),
|
||||
None => String::from("registry-1.docker.io"),
|
||||
},
|
||||
};
|
||||
return (
|
||||
registry.clone(),
|
||||
match c.name("repository") {
|
||||
Some(repository) => {
|
||||
let repo = repository.as_str().to_owned();
|
||||
if !repo.contains('/') {
|
||||
if !repo.contains('/') && registry == "registry-1.docker.io" {
|
||||
format!("library/{}", repo)
|
||||
} else {
|
||||
repo
|
||||
|
||||
Reference in New Issue
Block a user