Feat: Docker-Images bereinigen und in Update-Process integrieren

This commit is contained in:
2026-08-23 13:08:49 +02:00
parent c6a70180fe
commit 837eab18b0
+167 -26
View File
@@ -1,10 +1,10 @@
use crate::config;
use crate::log::{LogLevel, log};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::{Command, Output};
use serde::Deserialize;
use std::collections::HashMap;
use crate::log::{log, LogLevel};
use crate::config;
const COMPOSE_FILENAMES: &[&str] = &[
"docker-compose.yaml",
@@ -58,11 +58,21 @@ pub fn find_compose_file(dir: &Path) -> Option<PathBuf> {
/// Liest eine Compose-Datei ein und liefert eine Liste von (Service-Name, Image-Name).
pub fn parse_compose_services(compose_path: &Path) -> Result<Vec<(String, String)>, String> {
let content = fs::read_to_string(compose_path)
.map_err(|e| format!("Fehler beim Lesen der Datei '{}': {}", compose_path.display(), e))?;
let content = fs::read_to_string(compose_path).map_err(|e| {
format!(
"Fehler beim Lesen der Datei '{}': {}",
compose_path.display(),
e
)
})?;
let compose: ComposeFile = serde_yaml::from_str(&content)
.map_err(|e| format!("Fehler beim Parsen der YAML-Datei '{}': {}", compose_path.display(), e))?;
let compose: ComposeFile = serde_yaml::from_str(&content).map_err(|e| {
format!(
"Fehler beim Parsen der YAML-Datei '{}': {}",
compose_path.display(),
e
)
})?;
let mut services = Vec::new();
for (service_name, config) in compose.services {
@@ -126,7 +136,10 @@ pub fn pull_image(image: &str) -> Result<bool, String> {
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(format!("'docker pull {}' fehlgeschlagen: {}", image, stderr));
return Err(format!(
"'docker pull {}' fehlgeschlagen: {}",
image, stderr
));
}
let stdout_str = String::from_utf8_lossy(&output.stdout);
@@ -159,22 +172,53 @@ pub fn restart_compose_service(dir: &Path, service_name: Option<&str>) -> Result
Ok(())
}
/// Bereinigt ungenutzte Zwischen-Images via `docker image prune -f`.
pub fn prune_images() -> Result<(), String> {
let output = run_docker_command(&["image", "prune", "-f"])
.map_err(|e| format!("Fehler beim Ausführen von 'docker image prune -f': {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
return Err(format!(
"'docker image prune -f' fehlgeschlagen: {}",
stderr
));
}
Ok(())
}
/// Prüft alle Unterverzeichnisse im konfigurierten Apps-Verzeichnis auf Aktualisierungen.
pub fn run_updates() {
let config = config::get_config();
let apps_dir_path = Path::new(&config.general.apps_dir);
log("scanner", &format!("Überprüfe Anwendungsverzeichnis: {}", apps_dir_path.display()), LogLevel::Info);
log(
"scanner",
&format!(
"Überprüfe Anwendungsverzeichnis: {}",
apps_dir_path.display()
),
LogLevel::Info,
);
if !apps_dir_path.exists() {
log("scanner", &format!("Verzeichnis '{}' existiert nicht.", apps_dir_path.display()), LogLevel::Warn);
log(
"scanner",
&format!("Verzeichnis '{}' existiert nicht.", apps_dir_path.display()),
LogLevel::Warn,
);
return;
}
let entries = match fs::read_dir(apps_dir_path) {
Ok(e) => e,
Err(e) => {
log("scanner", &format!("Fehler beim Lesen von '{}': {}", apps_dir_path.display(), e), LogLevel::Error);
log(
"scanner",
&format!("Fehler beim Lesen von '{}': {}", apps_dir_path.display(), e),
LogLevel::Error,
);
return;
}
};
@@ -187,28 +231,58 @@ pub fn run_updates() {
app_dirs.sort();
if app_dirs.is_empty() {
log("scanner", &format!("Keine Unterverzeichnisse in '{}' gefunden.", apps_dir_path.display()), LogLevel::Info);
log(
"scanner",
&format!(
"Keine Unterverzeichnisse in '{}' gefunden.",
apps_dir_path.display()
),
LogLevel::Info,
);
return;
}
for app_dir in app_dirs {
let app_name = app_dir.file_name().unwrap_or_default().to_string_lossy();
log("scanner", &format!("Untersuche Anwendung '{}'...", app_name), LogLevel::Info);
log(
"scanner",
&format!("Untersuche Anwendung '{}'...", app_name),
LogLevel::Info,
);
let compose_file = match find_compose_file(&app_dir) {
Some(f) => f,
None => {
log("scanner", &format!("Keine Docker-Compose-Datei in '{}' gefunden. Überspringe.", app_dir.display()), LogLevel::Debug);
log(
"scanner",
&format!(
"Keine Docker-Compose-Datei in '{}' gefunden. Überspringe.",
app_dir.display()
),
LogLevel::Debug,
);
continue;
}
};
log("compose", &format!("Gefundene Compose-Datei: {}", compose_file.display()), LogLevel::Debug);
log(
"compose",
&format!("Gefundene Compose-Datei: {}", compose_file.display()),
LogLevel::Debug,
);
let services = match parse_compose_services(&compose_file) {
Ok(s) => s,
Err(e) => {
log("compose", &format!("Fehler beim Analysieren von '{}': {}", compose_file.display(), e), LogLevel::Warn);
log(
"compose",
&format!(
"Fehler beim Analysieren von '{}': {}",
compose_file.display(),
e
),
LogLevel::Warn,
);
continue;
}
};
@@ -217,40 +291,107 @@ pub fn run_updates() {
for (service_name, image) in services {
if !is_latest_image(&image) {
log("updater", &format!("Service '{}' verwendet kein :latest-Image ('{}'). Überspringe.", service_name, image), LogLevel::Debug);
log(
"updater",
&format!(
"Service '{}' verwendet kein :latest-Image ('{}'). Überspringe.",
service_name, image
),
LogLevel::Debug,
);
continue;
}
log("updater", &format!("Prüfe auf neues Image für Service '{}' ('{}')...", service_name, image), LogLevel::Info);
log(
"updater",
&format!(
"Prüfe auf neues Image für Service '{}' ('{}')...",
service_name, image
),
LogLevel::Info,
);
match pull_image(&image) {
Ok(true) => {
log("updater", &format!("Neueres Image für Service '{}' ('{}') gefunden und heruntergeladen.", service_name, image), LogLevel::Info);
log(
"updater",
&format!(
"Neueres Image für Service '{}' ('{}') gefunden und heruntergeladen.",
service_name, image
),
LogLevel::Info,
);
updated_any = true;
}
Ok(false) => {
log("updater", &format!("Image für Service '{}' ('{}') ist bereits aktuell.", service_name, image), LogLevel::Info);
log(
"updater",
&format!(
"Image für Service '{}' ('{}') ist bereits aktuell.",
service_name, image
),
LogLevel::Info,
);
}
Err(e) => {
log("updater", &format!("Fehler beim Aktualisieren von Image '{}': {}", image, e), LogLevel::Error);
log(
"updater",
&format!("Fehler beim Aktualisieren von Image '{}': {}", image, e),
LogLevel::Error,
);
}
}
}
if updated_any {
log("updater", &format!("Starte Anwendung '{}' neu mit aktualisierten Images...", app_name), LogLevel::Info);
log(
"updater",
&format!(
"Starte Anwendung '{}' neu mit aktualisierten Images...",
app_name
),
LogLevel::Info,
);
match restart_compose_service(&app_dir, None) {
Ok(_) => {
log("updater", &format!("Anwendung '{}' erfolgreich aktualisiert und neu gestartet.", app_name), LogLevel::Info);
log(
"updater",
&format!(
"Anwendung '{}' erfolgreich aktualisiert und neu gestartet.",
app_name
),
LogLevel::Info,
);
}
Err(e) => {
log("updater", &format!("Fehler beim Neustarten der Anwendung '{}': {}", app_name, e), LogLevel::Error);
log(
"updater",
&format!("Fehler beim Neustarten der Anwendung '{}': {}", app_name, e),
LogLevel::Error,
);
}
}
log(
"updater",
"Bereinige ungenutzte Docker-Images...",
LogLevel::Info,
);
if let Err(e) = prune_images() {
log(
"updater",
&format!("Fehler beim Bereinigen ungenutzter Docker-Images: {}", e),
LogLevel::Warn,
);
}
}
}
log("scanner", "Aktualisierungsvorgang abgeschlossen.", LogLevel::Info);
log(
"scanner",
"Aktualisierungsvorgang abgeschlossen.",
LogLevel::Info,
);
}
#[cfg(test)]