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::fs;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::process::{Command, Output}; use std::process::{Command, Output};
use serde::Deserialize;
use std::collections::HashMap;
use crate::log::{log, LogLevel};
use crate::config;
const COMPOSE_FILENAMES: &[&str] = &[ const COMPOSE_FILENAMES: &[&str] = &[
"docker-compose.yaml", "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). /// 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> { pub fn parse_compose_services(compose_path: &Path) -> Result<Vec<(String, String)>, String> {
let content = fs::read_to_string(compose_path) let content = fs::read_to_string(compose_path).map_err(|e| {
.map_err(|e| format!("Fehler beim Lesen der Datei '{}': {}", compose_path.display(), e))?; format!(
"Fehler beim Lesen der Datei '{}': {}",
compose_path.display(),
e
)
})?;
let compose: ComposeFile = serde_yaml::from_str(&content) let compose: ComposeFile = serde_yaml::from_str(&content).map_err(|e| {
.map_err(|e| format!("Fehler beim Parsen der YAML-Datei '{}': {}", compose_path.display(), e))?; format!(
"Fehler beim Parsen der YAML-Datei '{}': {}",
compose_path.display(),
e
)
})?;
let mut services = Vec::new(); let mut services = Vec::new();
for (service_name, config) in compose.services { for (service_name, config) in compose.services {
@@ -126,7 +136,10 @@ pub fn pull_image(image: &str) -> Result<bool, String> {
if !output.status.success() { if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string(); 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); 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(()) 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. /// Prüft alle Unterverzeichnisse im konfigurierten Apps-Verzeichnis auf Aktualisierungen.
pub fn run_updates() { pub fn run_updates() {
let config = config::get_config(); let config = config::get_config();
let apps_dir_path = Path::new(&config.general.apps_dir); 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() { 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; return;
} }
let entries = match fs::read_dir(apps_dir_path) { let entries = match fs::read_dir(apps_dir_path) {
Ok(e) => e, Ok(e) => e,
Err(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; return;
} }
}; };
@@ -187,28 +231,58 @@ pub fn run_updates() {
app_dirs.sort(); app_dirs.sort();
if app_dirs.is_empty() { 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; return;
} }
for app_dir in app_dirs { for app_dir in app_dirs {
let app_name = app_dir.file_name().unwrap_or_default().to_string_lossy(); 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) { let compose_file = match find_compose_file(&app_dir) {
Some(f) => f, Some(f) => f,
None => { 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; 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) { let services = match parse_compose_services(&compose_file) {
Ok(s) => s, Ok(s) => s,
Err(e) => { 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; continue;
} }
}; };
@@ -217,40 +291,107 @@ pub fn run_updates() {
for (service_name, image) in services { for (service_name, image) in services {
if !is_latest_image(&image) { 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; 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) { match pull_image(&image) {
Ok(true) => { 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; updated_any = true;
} }
Ok(false) => { 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) => { 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 { 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) { match restart_compose_service(&app_dir, None) {
Ok(_) => { 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) => { 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)] #[cfg(test)]