use super::prune::{self as docker_prune, PruneStat};
use crate::features::maintenance::MaintenanceContext;
use crate::store::daemon_settings;
use chrono::Utc;
use shared::{DockerCleanupConfig, DockerCleanupLastRun, DockerCleanupResourceResult, Event};
use std::time::Duration;
use tokio::sync::OwnedSemaphorePermit;
use tracing::{info, warn};
const RUN_TIMEOUT: Duration = Duration::from_secs(900);
pub async fn run_holding_slot(
state: &MaintenanceContext,
config: DockerCleanupConfig,
permit: OwnedSemaphorePermit,
) -> DockerCleanupConfig {
let fallback = config.clone();
let outcome = tokio::time::timeout(RUN_TIMEOUT, run(state, config)).await;
drop(permit);
match outcome {
Ok(config) => config,
Err(_) => finish_timed_out(state, fallback).await,
}
}
async fn finish_timed_out(
state: &MaintenanceContext,
mut config: DockerCleanupConfig,
) -> DockerCleanupConfig {
let message = format!(
"a limpeza passou de {}s e foi interrompida",
RUN_TIMEOUT.as_secs()
);
warn!("maintenance: {message}");
let now = Utc::now();
config.last_run_at = Some(now);
config.recompute_next_run(now);
if let Err(e) = save_config(state, &config).await {
warn!(error = %e, "maintenance: falha ao persistir config após timeout");
}
state.bus.publish(Event::DockerCleanupCompleted {
at: now,
results: vec![DockerCleanupResourceResult {
resource: "cleanup".to_string(),
count: 0,
reclaimed_bytes: 0,
error: Some(message),
}],
});
config
}
pub async fn run(
state: &MaintenanceContext,
mut config: DockerCleanupConfig,
) -> DockerCleanupConfig {
let mut results = Vec::new();
if config.containers {
results.push(record(
"containers",
docker_prune::prune_containers_core(state).await,
));
}
if config.images {
results.push(record(
"images",
docker_prune::prune_images_core(
state,
config.images_all,
config.images_older_than_hours,
)
.await,
));
}
if config.volumes {
results.push(record(
"volumes",
docker_prune::prune_volumes_core(state, config.volumes_all).await,
));
}
if config.networks {
results.push(record(
"networks",
docker_prune::prune_networks_core(state).await,
));
}
if config.build_cache {
results.push(record(
"build_cache",
docker_prune::prune_build_cache_core(state, config.build_cache_older_than_hours).await,
));
}
let now = Utc::now();
config.last_run_at = Some(now);
config.recompute_next_run(now);
if let Err(e) = save_config(state, &config).await {
warn!(error = %e, "maintenance: falha ao persistir config após execução");
}
let last_run = DockerCleanupLastRun {
at: now,
results: results.clone(),
};
if let Ok(json) = serde_json::to_string(&last_run)
&& let Err(e) = daemon_settings::set(
&state.db,
daemon_settings::KEY_DOCKER_CLEANUP_LAST_RUN,
&json,
)
.await
{
warn!(error = %e, "maintenance: falha ao persistir resumo da execução");
}
info!(count = results.len(), "maintenance: limpeza concluída");
state
.bus
.publish(Event::DockerCleanupCompleted { at: now, results });
config
}
fn record(resource: &str, result: Result<PruneStat, String>) -> DockerCleanupResourceResult {
match result {
Ok(s) => DockerCleanupResourceResult {
resource: resource.to_string(),
count: s.count,
reclaimed_bytes: s.reclaimed_bytes,
error: None,
},
Err(e) => {
warn!(resource, error = %e, "maintenance: falha ao limpar recurso");
DockerCleanupResourceResult {
resource: resource.to_string(),
count: 0,
reclaimed_bytes: 0,
error: Some(e),
}
}
}
}
pub(crate) async fn save_config(
state: &MaintenanceContext,
config: &DockerCleanupConfig,
) -> anyhow::Result<()> {
let json = serde_json::to_string(config)?;
daemon_settings::set(&state.db, daemon_settings::KEY_DOCKER_CLEANUP_CONFIG, &json).await
}
pub(crate) async fn load_config(state: &MaintenanceContext) -> anyhow::Result<DockerCleanupConfig> {
let raw = daemon_settings::get(&state.db, daemon_settings::KEY_DOCKER_CLEANUP_CONFIG).await?;
Ok(match raw {
Some(json) => serde_json::from_str(&json).unwrap_or_default(),
None => DockerCleanupConfig::default(),
})
}
pub(crate) async fn load_last_run(state: &MaintenanceContext) -> Option<DockerCleanupLastRun> {
let raw = daemon_settings::get(&state.db, daemon_settings::KEY_DOCKER_CLEANUP_LAST_RUN)
.await
.ok()
.flatten()?;
serde_json::from_str(&raw).ok()
}