mirror of
https://github.com/sergi0g/cup.git
synced 2025-11-08 13:13:49 -05:00
Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
60096792a9 | ||
|
|
a599d4e084 | ||
|
|
a5a1f12899 | ||
|
|
766a20ccac | ||
|
|
fe66ba842a | ||
|
|
c06c20394f | ||
|
|
98dafb8ba4 | ||
|
|
2addfca1b4 | ||
|
|
e3b05923ae | ||
|
|
aa4195f8d6 | ||
|
|
1b94629c79 | ||
|
|
8cd9cce94e | ||
|
|
ddabd8c102 | ||
|
|
0b0028ab6d | ||
|
|
75509550b1 | ||
|
|
9716d1a351 | ||
|
|
d5a2556768 | ||
|
|
e7f1921620 | ||
|
|
7ea6ae6de5 | ||
|
|
d7c2e6436c | ||
|
|
fde61ee07d | ||
|
|
c4de3961a0 | ||
|
|
404c574c2c | ||
|
|
6d4df20f54 | ||
|
|
7b3745d095 | ||
|
|
f9aa516da7 |
12
.github/actions/build-image/Dockerfile
vendored
Normal file
12
.github/actions/build-image/Dockerfile
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
FROM --platform=$BUILDPLATFORM alpine AS builder
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETOS
|
||||
|
||||
COPY binaries/* /
|
||||
RUN mv cup-$TARGETOS-$TARGETARCH cup
|
||||
RUN chmod +x cup
|
||||
|
||||
FROM scratch
|
||||
COPY --from=builder /cup /cup
|
||||
ENTRYPOINT ["/cup"]
|
||||
52
.github/actions/build-image/action.yml
vendored
Normal file
52
.github/actions/build-image/action.yml
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
name: Build Image
|
||||
inputs:
|
||||
tags:
|
||||
description: "Docker image tags"
|
||||
required: true
|
||||
gh-token:
|
||||
description: "Github token"
|
||||
required: true
|
||||
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
path: .
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
ghcr.io/sergi0g/cup
|
||||
tags: ${{ inputs.tags }}
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: sergi0g
|
||||
password: ${{ inputs.gh-token }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
file: ./.github/actions/build-image/Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
36
.github/workflows/nightly.yml
vendored
36
.github/workflows/nightly.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
|
||||
- name: Set up Bun
|
||||
uses: oven-sh/setup-bun@v1
|
||||
|
||||
@@ -62,38 +62,24 @@ jobs:
|
||||
cup-linux-arm64
|
||||
|
||||
build-image:
|
||||
needs: get-tag
|
||||
needs:
|
||||
- get-tag
|
||||
- build-binaries
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
- uses: ./.github/actions/build-image
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/arm64
|
||||
push: true
|
||||
tags: ghcr.io/sergi0g/cup:${{ needs.get-tag.outputs.tag }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: |
|
||||
${{ needs.get-tag.outputs.tag }}
|
||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
nightly-release:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-binaries, get-tag]
|
||||
needs:
|
||||
- build-binaries
|
||||
- build-image
|
||||
steps:
|
||||
- name: Download binaries
|
||||
uses: actions/download-artifact@v4
|
||||
|
||||
33
.github/workflows/release.yml
vendored
33
.github/workflows/release.yml
vendored
@@ -60,34 +60,19 @@ jobs:
|
||||
cup-linux-arm64
|
||||
|
||||
build-image:
|
||||
needs: get-tag
|
||||
needs:
|
||||
- get-tag
|
||||
- build-binaries
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v3
|
||||
- uses: ./.github/actions/build-image
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push image
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
platforms: linux/amd64, linux/arm64
|
||||
push: true
|
||||
tags: ghcr.io/sergi0g/cup:${{ needs.get-tag.outputs.tag }},ghcr.io/sergi0g/cup:latest
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
tags: |
|
||||
${{ needs.get-tag.outputs.tag }}
|
||||
latest
|
||||
gh-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
@@ -107,4 +92,4 @@ jobs:
|
||||
prerelease: true
|
||||
tag_name: ${{ needs.get-tag.outputs.tag }}
|
||||
name: ${{ needs.get-tag.outputs.tag }}
|
||||
files: binaries/*
|
||||
files: binaries/*
|
||||
|
||||
1167
Cargo.lock
generated
1167
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
10
Cargo.toml
10
Cargo.toml
@@ -1,15 +1,15 @@
|
||||
[package]
|
||||
name = "cup"
|
||||
version = "3.0.0"
|
||||
version = "3.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.7", features = ["derive"] }
|
||||
indicatif = { version = "0.17.8", optional = true }
|
||||
tokio = { version = "1.38.0", features = ["macros", "rt-multi-thread"] }
|
||||
xitca-web = { version = "0.5.0", optional = true }
|
||||
xitca-web = { version = "0.6.2", optional = true }
|
||||
liquid = { version = "0.26.6", optional = true }
|
||||
bollard = "0.16.1"
|
||||
bollard = "0.18.1"
|
||||
once_cell = "1.19.0"
|
||||
http-auth = { version = "0.1.9", default-features = false }
|
||||
termsize = { version = "0.1.8", optional = true }
|
||||
@@ -17,11 +17,11 @@ regex = { version = "1.10.5", default-features = false, features = ["perf"] }
|
||||
chrono = { version = "0.4.38", default-features = false, features = ["std", "alloc", "clock"], optional = true }
|
||||
reqwest = { version = "0.12.7", default-features = false, features = ["rustls-tls"] }
|
||||
futures = "0.3.30"
|
||||
reqwest-retry = "0.6.1"
|
||||
reqwest-retry = "0.7.0"
|
||||
reqwest-middleware = "0.3.3"
|
||||
rustc-hash = "2.0.0"
|
||||
http-link = "1.0.1"
|
||||
itertools = "0.13.0"
|
||||
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 }
|
||||
|
||||
@@ -15,7 +15,7 @@ RUN ~/.bun/bin/bun install
|
||||
RUN ~/.bun/bin/bun run build
|
||||
|
||||
### Build Cup ###
|
||||
FROM rust:1.80.1-alpine AS build
|
||||
FROM rust:1-alpine AS build
|
||||
|
||||
# Requirements
|
||||
RUN apk add musl-dev
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Cup 🥤
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
|
||||
Cup is the easiest way to check for container image updates.
|
||||
|
||||

|
||||
|
||||
BIN
docs/bun.lockb
BIN
docs/bun.lockb
Binary file not shown.
@@ -4,7 +4,7 @@
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
"build": "next build && pagefind --site out --output-path out/_pagefind",
|
||||
"start": "next start",
|
||||
"lint": "next lint",
|
||||
"fmt": "bun prettier --write ."
|
||||
@@ -26,6 +26,7 @@
|
||||
"@types/react-dom": "^19.0.3",
|
||||
"eslint": "^9.18.0",
|
||||
"eslint-config-next": "15.1.5",
|
||||
"pagefind": "^1.3.0",
|
||||
"postcss": "^8.5.1",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
|
||||
BIN
docs/src/app/assets/ha-cup-component.png
Normal file
BIN
docs/src/app/assets/ha-cup-component.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 94 KiB |
47
docs/src/content/docs/community-resources/home-assistant.mdx
Normal file
47
docs/src/content/docs/community-resources/home-assistant.mdx
Normal file
@@ -0,0 +1,47 @@
|
||||
import Image from "next/image";
|
||||
|
||||
import screenshot from "@/app/assets/ha-cup-component.png";
|
||||
|
||||
# Home Assistant integration
|
||||
|
||||
Many thanks to [@bastgau](https://github.com/bastgau) for creating this integration.
|
||||
|
||||
## About
|
||||
|
||||
The **HA Cup Component** integration for Home Assistant allows you to retrieve update statistics for Docker containers directly from your Home Assistant interface.
|
||||
|
||||
With this integration, you can easily track the status of your Docker containers and receive notifications when updates are available.
|
||||
|
||||
The following sensors are currently implemented:
|
||||
|
||||
<Image
|
||||
src={screenshot}
|
||||
alt="Screenshot of Home Assistant showing a card with update information provided by Cup"
|
||||
/>
|
||||
|
||||
## Installation
|
||||
|
||||
### Via HACS
|
||||
|
||||
1. Open Home Assistant and go to HACS
|
||||
2. Navigate to "Integrations" and click on "Add a custom repository".
|
||||
3. Use https://github.com/bastgau/ha-cup-component as the URL
|
||||
4. Search for "HA Cup Component" and install it.
|
||||
5. Restart Home Assistant.
|
||||
|
||||
### One-click install
|
||||
|
||||
[](https://my.home-assistant.io/redirect/hacs_repository/?owner=bastgau&repository=ha-cup-component&category=Integration)
|
||||
|
||||
### Manual Installation
|
||||
|
||||
1. Download the integration files from the GitHub repository.
|
||||
2. Place the integration folder in the custom_components directory of Home Assistant.
|
||||
3. Restart Home Assistant.
|
||||
|
||||
## Support & Contributions
|
||||
|
||||
If you encounter any issues or wish to contribute to improving this integration, feel free to open an issue or a pull request in the [GitHub repository](https://github.com/bastgau/ha-cup-component).
|
||||
|
||||
Support the author:
|
||||
[](https://www.buymeacoffee.com/bastgau)
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Callout, Cards } from "nextra/components";
|
||||
import { IconServer, IconTerminal } from "@tabler/icons-react"
|
||||
import { IconServer, IconTerminal } from "@tabler/icons-react";
|
||||
|
||||
# Integrations
|
||||
|
||||
@@ -34,6 +34,7 @@ The data returned from the API or from the CLI is in JSON and looks like this:
|
||||
"repository": "sergi0g/cup",
|
||||
"tag": "latest",
|
||||
},
|
||||
"url": "https://github.com/sergi0g/cup", // The URL specified in the "org.opencontainers.image.url" label, otherwise null
|
||||
"result": {
|
||||
"has_update": true, // `true` when an image has an update of any kind, `false` when up to date and `null` when unknown.
|
||||
"info": {
|
||||
@@ -77,3 +78,7 @@ For retrieving the above data, refer to the CLI and server pages:
|
||||
href="/docs/usage/server"
|
||||
/>
|
||||
</Cards>
|
||||
|
||||
## Refresh Cup
|
||||
|
||||
If you'd like to fetch the latest information, you can manually trigger a refresh by making a `GET` request to the `/api/v3/refresh` endpoint. Once the request completes, you can fetch the data as described above.
|
||||
|
||||
27
src/check.rs
27
src/check.rs
@@ -120,6 +120,16 @@ pub async fn get_updates(
|
||||
.iter()
|
||||
.map(|image| &image.parts.registry)
|
||||
.unique()
|
||||
.filter(|®istry| match ctx.config.registries.get(registry) {
|
||||
Some(config) => {
|
||||
if config.ignore {
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
None => true,
|
||||
})
|
||||
.collect::<Vec<&String>>();
|
||||
|
||||
// Create request client. All network requests share the same client for better performance.
|
||||
@@ -138,7 +148,7 @@ pub async fn get_updates(
|
||||
|
||||
// Retrieve an authentication token (if required) for each registry.
|
||||
let mut tokens: FxHashMap<&str, Option<String>> = FxHashMap::default();
|
||||
for registry in registries {
|
||||
for registry in registries.clone() {
|
||||
let credentials = if let Some(registry_config) = ctx.config.registries.get(registry) {
|
||||
®istry_config.authentication
|
||||
} else {
|
||||
@@ -163,24 +173,11 @@ pub async fn get_updates(
|
||||
|
||||
ctx.logger.debug(format!("Tokens: {:?}", tokens));
|
||||
|
||||
let ignored_registries = ctx
|
||||
.config
|
||||
.registries
|
||||
.iter()
|
||||
.filter_map(|(registry, registry_config)| {
|
||||
if registry_config.ignore {
|
||||
Some(registry)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<&String>>();
|
||||
|
||||
let mut handles = Vec::with_capacity(images.len());
|
||||
|
||||
// Loop through images check for updates
|
||||
for image in &images {
|
||||
let is_ignored = ignored_registries.contains(&&image.parts.registry)
|
||||
let is_ignored = !registries.contains(&&image.parts.registry)
|
||||
|| ctx
|
||||
.config
|
||||
.images
|
||||
|
||||
@@ -95,6 +95,10 @@ impl Client {
|
||||
let message = format!("{} {}: Connection timed out!", method, url);
|
||||
self.ctx.logger.warn(&message);
|
||||
Err(message)
|
||||
} else if error.is_middleware() {
|
||||
let message = format!("{} {}: Connection failed after 3 retries!", method, url);
|
||||
self.ctx.logger.warn(&message);
|
||||
Err(message)
|
||||
} else {
|
||||
error!(
|
||||
"{} {}: Unexpected error: {}",
|
||||
|
||||
@@ -35,6 +35,7 @@ pub struct VersionInfo {
|
||||
pub struct Image {
|
||||
pub reference: String,
|
||||
pub parts: Parts,
|
||||
pub url: Option<String>,
|
||||
pub digest_info: Option<DigestInfo>,
|
||||
pub version_info: Option<VersionInfo>,
|
||||
pub error: Option<String>,
|
||||
@@ -61,6 +62,7 @@ impl Image {
|
||||
repository,
|
||||
tag,
|
||||
},
|
||||
url: image.url(),
|
||||
digest_info: Some(DigestInfo {
|
||||
local_digests,
|
||||
remote_digest: None,
|
||||
@@ -141,6 +143,7 @@ impl Image {
|
||||
Update {
|
||||
reference: self.reference.clone(),
|
||||
parts: self.parts.clone(),
|
||||
url: self.url.clone(),
|
||||
result: UpdateResult {
|
||||
has_update: has_update.to_option_bool(),
|
||||
info: match has_update {
|
||||
|
||||
@@ -3,6 +3,7 @@ use bollard::secret::{ImageInspect, ImageSummary};
|
||||
pub trait InspectData {
|
||||
fn tags(&self) -> Option<&Vec<String>>;
|
||||
fn digests(&self) -> Option<&Vec<String>>;
|
||||
fn url(&self) -> Option<String>;
|
||||
}
|
||||
|
||||
impl InspectData for ImageInspect {
|
||||
@@ -13,6 +14,16 @@ impl InspectData for ImageInspect {
|
||||
fn digests(&self) -> Option<&Vec<String>> {
|
||||
self.repo_digests.as_ref()
|
||||
}
|
||||
|
||||
fn url(&self) -> Option<String> {
|
||||
match &self.config {
|
||||
Some(config) => match &config.labels {
|
||||
Some(labels) => labels.get("org.opencontainers.image.url").cloned(),
|
||||
None => None,
|
||||
},
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InspectData for ImageSummary {
|
||||
@@ -23,4 +34,8 @@ impl InspectData for ImageSummary {
|
||||
fn digests(&self) -> Option<&Vec<String>> {
|
||||
Some(&self.repo_digests)
|
||||
}
|
||||
|
||||
fn url(&self) -> Option<String> {
|
||||
self.labels.get("org.opencontainers.image.url").cloned()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ use super::{parts::Parts, status::Status};
|
||||
pub struct Update {
|
||||
pub reference: String,
|
||||
pub parts: Parts,
|
||||
pub url: Option<String>,
|
||||
pub result: UpdateResult,
|
||||
pub time: u32,
|
||||
pub server: Option<String>,
|
||||
|
||||
@@ -16,7 +16,7 @@ pub fn split(reference: &str) -> (String, String, String) {
|
||||
}
|
||||
}
|
||||
};
|
||||
let splits = repository_and_tag.split(':').collect::<Vec<&str>>();
|
||||
let splits = repository_and_tag.split('@').next().unwrap().split(':').collect::<Vec<&str>>();
|
||||
let (repository, tag) = match splits.len() {
|
||||
1 | 2 => {
|
||||
let repository_components = splits[0].split('/').collect::<Vec<&str>>();
|
||||
@@ -38,7 +38,7 @@ pub fn split(reference: &str) -> (String, String, String) {
|
||||
};
|
||||
(repository, tag)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
_ => {dbg!(splits); panic!()},
|
||||
};
|
||||
(registry.to_string(), repository, tag.to_string())
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
nodejs 22.8.0
|
||||
@@ -21,6 +21,8 @@ export function CodeBlock({
|
||||
};
|
||||
};
|
||||
|
||||
const copyText = children instanceof Array ? children.join("") : children;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`group relative flex w-full items-center rounded-lg bg-${theme}-100 px-3 py-2 font-mono text-${theme}-700 dark:bg-${theme}-950 dark:text-${theme}-300`}
|
||||
@@ -35,7 +37,7 @@ export function CodeBlock({
|
||||
) : (
|
||||
<button
|
||||
className={`duration-50 absolute right-3 bg-${theme}-100 py-1 pl-2 opacity-0 transition-opacity group-hover:opacity-100 dark:bg-${theme}-950`}
|
||||
onClick={handleCopy(`docker pull ${children}`)}
|
||||
onClick={handleCopy(`${copyText}`)}
|
||||
>
|
||||
<Clipboard className="size-5" />
|
||||
</button>
|
||||
|
||||
@@ -40,7 +40,9 @@ export default function Image({ data }: { data: Image }) {
|
||||
: data.reference;
|
||||
const info = getInfo(data)!;
|
||||
let url: string | null = null;
|
||||
if (clickable_registries.includes(data.parts.registry)) {
|
||||
if (data.url) {
|
||||
url = data.url;
|
||||
} else if (clickable_registries.includes(data.parts.registry)) {
|
||||
switch (data.parts.registry) {
|
||||
case "registry-1.docker.io":
|
||||
url = `https://hub.docker.com/r/${data.parts.repository}`;
|
||||
@@ -82,14 +84,14 @@ export default function Image({ data }: { data: Image }) {
|
||||
>
|
||||
<div className="mb-4 flex items-center gap-3">
|
||||
<Box className={`size-6 shrink-0 text-${theme}-500`} />
|
||||
<DialogTitle className="font-mono text-black dark:text-white">
|
||||
<DialogTitle className="break-all font-mono text-black dark:text-white">
|
||||
{url ? (
|
||||
<>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className={`group w-fit text-black hover:underline dark:text-white`}
|
||||
className={`group w-fit hover:underline`}
|
||||
>
|
||||
<span>
|
||||
{data.reference}
|
||||
|
||||
@@ -20,6 +20,7 @@ export interface Image {
|
||||
repository: string;
|
||||
tag: string;
|
||||
};
|
||||
url: string | null;
|
||||
result: {
|
||||
has_update: boolean | null;
|
||||
info: VersionInfo | DigestInfo | null;
|
||||
|
||||
Reference in New Issue
Block a user