Feat: Docker-Images bereinigen und in Update-Process integrieren
This commit is contained in:
+167
-26
@@ -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)]
|
||||||
|
|||||||
Reference in New Issue
Block a user