use crate::command::{CommandExecutor, DockerCommand};
use crate::error::Result;
use async_trait::async_trait;
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ImageUsage {
#[serde(default)]
pub total_count: usize,
#[serde(default)]
pub active: usize,
#[serde(default)]
pub size: i64,
#[serde(default)]
pub reclaimable_size: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ContainerUsage {
#[serde(default)]
pub total_count: usize,
#[serde(default)]
pub running: usize,
#[serde(default)]
pub paused: usize,
#[serde(default)]
pub stopped: usize,
#[serde(default)]
pub size: i64,
#[serde(default)]
pub reclaimable_size: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct VolumeUsage {
#[serde(default)]
pub total_count: usize,
#[serde(default)]
pub active: usize,
#[serde(default)]
pub size: i64,
#[serde(default)]
pub reclaimable_size: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BuildCacheUsage {
#[serde(default)]
pub total_count: usize,
#[serde(default)]
pub active: usize,
#[serde(default)]
pub size: i64,
#[serde(default)]
pub reclaimable_size: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct DiskUsage {
pub images: Vec<ImageInfo>,
pub containers: Vec<ContainerInfo>,
pub volumes: Vec<VolumeInfo>,
#[serde(default)]
pub build_cache: Vec<BuildCacheInfo>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ImageInfo {
#[serde(rename = "ID")]
pub id: String,
#[serde(default)]
pub repository: String,
#[serde(default)]
pub tag: String,
#[serde(default)]
pub created: i64,
#[serde(default)]
pub size: i64,
#[serde(default)]
pub shared_size: i64,
#[serde(default)]
pub virtual_size: i64,
#[serde(default)]
pub containers: i32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct ContainerInfo {
#[serde(rename = "ID")]
pub id: String,
#[serde(default)]
pub names: Vec<String>,
#[serde(default)]
pub image: String,
#[serde(default)]
pub created: i64,
#[serde(default)]
pub state: String,
#[serde(default)]
pub status: String,
#[serde(default, rename = "SizeRw")]
pub size_rw: i64,
#[serde(default, rename = "SizeRootFs")]
pub size_root_fs: i64,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct VolumeInfo {
pub name: String,
#[serde(default)]
pub driver: String,
#[serde(default)]
pub mount_point: String,
#[serde(default)]
pub created_at: String,
#[serde(default)]
pub size: i64,
#[serde(default, rename = "RefCount")]
pub ref_count: i32,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "PascalCase")]
pub struct BuildCacheInfo {
#[serde(rename = "ID")]
pub id: String,
#[serde(default)]
pub parent: String,
#[serde(default, rename = "Type")]
pub cache_type: String,
#[serde(default)]
pub description: String,
#[serde(default)]
pub created_at: String,
#[serde(default)]
pub last_used_at: String,
#[serde(default)]
pub usage_count: i64,
#[serde(default)]
pub size: i64,
#[serde(default)]
pub in_use: bool,
#[serde(default)]
pub shared: bool,
}
#[derive(Debug, Clone)]
pub struct SystemDfCommand {
verbose: bool,
format: Option<String>,
pub executor: CommandExecutor,
}
impl SystemDfCommand {
#[must_use]
pub fn new() -> Self {
Self {
verbose: false,
format: None,
executor: CommandExecutor::new(),
}
}
#[must_use]
pub fn verbose(mut self) -> Self {
self.verbose = true;
self
}
#[must_use]
pub fn format(mut self, template: impl Into<String>) -> Self {
self.format = Some(template.into());
self
}
pub async fn run(&self) -> Result<DiskUsage> {
self.execute().await
}
}
impl Default for SystemDfCommand {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl DockerCommand for SystemDfCommand {
type Output = DiskUsage;
fn build_command_args(&self) -> Vec<String> {
let mut args = vec!["system".to_string(), "df".to_string()];
if self.verbose {
args.push("--verbose".to_string());
}
args.push("--format".to_string());
args.push("json".to_string());
args.extend(self.executor.raw_args.clone());
args
}
fn get_executor(&self) -> &CommandExecutor {
&self.executor
}
fn get_executor_mut(&mut self) -> &mut CommandExecutor {
&mut self.executor
}
async fn execute(&self) -> Result<Self::Output> {
let args = self.build_command_args();
let command_name = args[0].clone();
let command_args = args[1..].to_vec();
let output = self
.executor
.execute_command(&command_name, command_args)
.await?;
let stdout = &output.stdout;
let usage: DiskUsage =
serde_json::from_str(stdout).map_err(|e| crate::error::Error::ParseError {
message: format!("Failed to parse disk usage: {e}"),
})?;
Ok(usage)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_system_df_builder() {
let cmd = SystemDfCommand::new().verbose();
let args = cmd.build_command_args();
assert_eq!(args[0], "system");
assert!(args.contains(&"df".to_string()));
assert!(args.contains(&"--verbose".to_string()));
assert!(args.contains(&"--format".to_string()));
assert!(args.contains(&"json".to_string()));
}
}