docker_wrapper/command/system/
df.rs

1//! Docker system df command implementation.
2
3use crate::command::{CommandExecutor, DockerCommand};
4use crate::error::Result;
5use async_trait::async_trait;
6use serde::Deserialize;
7
8/// Image disk usage information
9#[derive(Debug, Clone, Deserialize)]
10#[serde(rename_all = "PascalCase")]
11pub struct ImageUsage {
12    /// Total number of images
13    #[serde(default)]
14    pub total_count: usize,
15
16    /// Number of active images
17    #[serde(default)]
18    pub active: usize,
19
20    /// Total size in bytes
21    #[serde(default)]
22    pub size: i64,
23
24    /// Reclaimable size in bytes
25    #[serde(default)]
26    pub reclaimable_size: i64,
27}
28
29/// Container disk usage information
30#[derive(Debug, Clone, Deserialize)]
31#[serde(rename_all = "PascalCase")]
32pub struct ContainerUsage {
33    /// Total number of containers
34    #[serde(default)]
35    pub total_count: usize,
36
37    /// Number of running containers
38    #[serde(default)]
39    pub running: usize,
40
41    /// Number of paused containers
42    #[serde(default)]
43    pub paused: usize,
44
45    /// Number of stopped containers
46    #[serde(default)]
47    pub stopped: usize,
48
49    /// Total size in bytes
50    #[serde(default)]
51    pub size: i64,
52
53    /// Reclaimable size in bytes
54    #[serde(default)]
55    pub reclaimable_size: i64,
56}
57
58/// Volume disk usage information
59#[derive(Debug, Clone, Deserialize)]
60#[serde(rename_all = "PascalCase")]
61pub struct VolumeUsage {
62    /// Total number of volumes
63    #[serde(default)]
64    pub total_count: usize,
65
66    /// Number of active volumes
67    #[serde(default)]
68    pub active: usize,
69
70    /// Total size in bytes
71    #[serde(default)]
72    pub size: i64,
73
74    /// Reclaimable size in bytes
75    #[serde(default)]
76    pub reclaimable_size: i64,
77}
78
79/// Build cache disk usage information
80#[derive(Debug, Clone, Deserialize)]
81#[serde(rename_all = "PascalCase")]
82pub struct BuildCacheUsage {
83    /// Total number of build cache entries
84    #[serde(default)]
85    pub total_count: usize,
86
87    /// Number of active build cache entries
88    #[serde(default)]
89    pub active: usize,
90
91    /// Total size in bytes
92    #[serde(default)]
93    pub size: i64,
94
95    /// Reclaimable size in bytes
96    #[serde(default)]
97    pub reclaimable_size: i64,
98}
99
100/// Docker disk usage information
101#[derive(Debug, Clone, Deserialize)]
102#[serde(rename_all = "PascalCase")]
103pub struct DiskUsage {
104    /// Image disk usage
105    pub images: Vec<ImageInfo>,
106
107    /// Container disk usage
108    pub containers: Vec<ContainerInfo>,
109
110    /// Volume disk usage
111    pub volumes: Vec<VolumeInfo>,
112
113    /// Build cache disk usage
114    #[serde(default)]
115    pub build_cache: Vec<BuildCacheInfo>,
116}
117
118/// Detailed image information
119#[derive(Debug, Clone, Deserialize)]
120#[serde(rename_all = "PascalCase")]
121pub struct ImageInfo {
122    /// Image ID
123    #[serde(rename = "ID")]
124    pub id: String,
125
126    /// Repository
127    #[serde(default)]
128    pub repository: String,
129
130    /// Tag
131    #[serde(default)]
132    pub tag: String,
133
134    /// Created timestamp
135    #[serde(default)]
136    pub created: i64,
137
138    /// Size in bytes
139    #[serde(default)]
140    pub size: i64,
141
142    /// Shared size in bytes
143    #[serde(default)]
144    pub shared_size: i64,
145
146    /// Virtual size in bytes
147    #[serde(default)]
148    pub virtual_size: i64,
149
150    /// Number of containers using this image
151    #[serde(default)]
152    pub containers: i32,
153}
154
155/// Detailed container information
156#[derive(Debug, Clone, Deserialize)]
157#[serde(rename_all = "PascalCase")]
158pub struct ContainerInfo {
159    /// Container ID
160    #[serde(rename = "ID")]
161    pub id: String,
162
163    /// Container names
164    #[serde(default)]
165    pub names: Vec<String>,
166
167    /// Image
168    #[serde(default)]
169    pub image: String,
170
171    /// Created timestamp
172    #[serde(default)]
173    pub created: i64,
174
175    /// State
176    #[serde(default)]
177    pub state: String,
178
179    /// Status
180    #[serde(default)]
181    pub status: String,
182
183    /// Size in bytes (read/write layer)
184    #[serde(default, rename = "SizeRw")]
185    pub size_rw: i64,
186
187    /// Root filesystem size in bytes
188    #[serde(default, rename = "SizeRootFs")]
189    pub size_root_fs: i64,
190}
191
192/// Detailed volume information
193#[derive(Debug, Clone, Deserialize)]
194#[serde(rename_all = "PascalCase")]
195pub struct VolumeInfo {
196    /// Volume name
197    pub name: String,
198
199    /// Driver
200    #[serde(default)]
201    pub driver: String,
202
203    /// Mount point
204    #[serde(default)]
205    pub mount_point: String,
206
207    /// Created timestamp
208    #[serde(default)]
209    pub created_at: String,
210
211    /// Size in bytes
212    #[serde(default)]
213    pub size: i64,
214
215    /// Number of containers using this volume
216    #[serde(default, rename = "RefCount")]
217    pub ref_count: i32,
218}
219
220/// Build cache information
221#[derive(Debug, Clone, Deserialize)]
222#[serde(rename_all = "PascalCase")]
223pub struct BuildCacheInfo {
224    /// Cache ID
225    #[serde(rename = "ID")]
226    pub id: String,
227
228    /// Parent ID
229    #[serde(default)]
230    pub parent: String,
231
232    /// Cache type
233    #[serde(default, rename = "Type")]
234    pub cache_type: String,
235
236    /// Description
237    #[serde(default)]
238    pub description: String,
239
240    /// Created timestamp
241    #[serde(default)]
242    pub created_at: String,
243
244    /// Last used timestamp
245    #[serde(default)]
246    pub last_used_at: String,
247
248    /// Usage count
249    #[serde(default)]
250    pub usage_count: i64,
251
252    /// Size in bytes
253    #[serde(default)]
254    pub size: i64,
255
256    /// Whether the cache is in use
257    #[serde(default)]
258    pub in_use: bool,
259
260    /// Whether the cache is shared
261    #[serde(default)]
262    pub shared: bool,
263}
264
265/// Docker system df command
266///
267/// Show docker disk usage
268#[derive(Debug, Clone)]
269pub struct SystemDfCommand {
270    /// Show detailed information
271    verbose: bool,
272
273    /// Format output using a custom template
274    format: Option<String>,
275
276    /// Command executor
277    pub executor: CommandExecutor,
278}
279
280impl SystemDfCommand {
281    /// Create a new system df command
282    #[must_use]
283    pub fn new() -> Self {
284        Self {
285            verbose: false,
286            format: None,
287            executor: CommandExecutor::new(),
288        }
289    }
290
291    /// Show detailed information
292    #[must_use]
293    pub fn verbose(mut self) -> Self {
294        self.verbose = true;
295        self
296    }
297
298    /// Format output using a custom template
299    #[must_use]
300    pub fn format(mut self, template: impl Into<String>) -> Self {
301        self.format = Some(template.into());
302        self
303    }
304
305    /// Execute the system df command
306    ///
307    /// # Errors
308    ///
309    /// Returns an error if the command fails to execute or if Docker is not available.
310    pub async fn run(&self) -> Result<DiskUsage> {
311        self.execute().await
312    }
313}
314
315impl Default for SystemDfCommand {
316    fn default() -> Self {
317        Self::new()
318    }
319}
320
321#[async_trait]
322impl DockerCommand for SystemDfCommand {
323    type Output = DiskUsage;
324
325    fn build_command_args(&self) -> Vec<String> {
326        let mut args = vec!["system".to_string(), "df".to_string()];
327
328        if self.verbose {
329            args.push("--verbose".to_string());
330        }
331
332        // Always use JSON format for parsing
333        args.push("--format".to_string());
334        args.push("json".to_string());
335
336        args.extend(self.executor.raw_args.clone());
337        args
338    }
339
340    fn get_executor(&self) -> &CommandExecutor {
341        &self.executor
342    }
343
344    fn get_executor_mut(&mut self) -> &mut CommandExecutor {
345        &mut self.executor
346    }
347
348    async fn execute(&self) -> Result<Self::Output> {
349        let args = self.build_command_args();
350        let command_name = args[0].clone();
351        let command_args = args[1..].to_vec();
352        let output = self
353            .executor
354            .execute_command(&command_name, command_args)
355            .await?;
356        let stdout = &output.stdout;
357
358        // Parse JSON output
359        let usage: DiskUsage =
360            serde_json::from_str(stdout).map_err(|e| crate::error::Error::ParseError {
361                message: format!("Failed to parse disk usage: {e}"),
362            })?;
363
364        Ok(usage)
365    }
366}
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371
372    #[test]
373    fn test_system_df_builder() {
374        let cmd = SystemDfCommand::new().verbose();
375
376        let args = cmd.build_command_args();
377        assert_eq!(args[0], "system");
378        assert!(args.contains(&"df".to_string()));
379        assert!(args.contains(&"--verbose".to_string()));
380        assert!(args.contains(&"--format".to_string()));
381        assert!(args.contains(&"json".to_string()));
382    }
383}