Skip to main content

zlayer_types/api/
system.rs

1//! System disk-usage and prune API DTOs.
2//!
3//! Wire-format types shared between the daemon's `zlayer system df` /
4//! `zlayer system prune` endpoints and SDK clients. The daemon
5//! (`zlayer-api`) returns these shapes and the client (`zlayer-client`)
6//! decodes them. Moved out of `zlayer-api` so SDK crates can depend on
7//! them without pulling in the full server stack.
8
9use serde::{Deserialize, Serialize};
10use utoipa::ToSchema;
11
12/// Disk usage for one `ZLayer` storage category (mirrors `docker system df`).
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, ToSchema)]
14pub struct DiskUsageCategory {
15    /// Category name: "images", "containers", "volumes", "build-cache",
16    /// "layers", "toolchains", "vms", "wasm", "logs", "tmp", "registry", ...
17    pub name: String,
18    /// Total on-disk bytes for this category.
19    pub total_bytes: u64,
20    /// Bytes reclaimable (unreferenced/dangling/dead). For shared layer stores
21    /// this is "not referenced by any live image/container".
22    pub reclaimable_bytes: u64,
23    /// Item count (images, containers, volumes, layers, ...).
24    pub item_count: u64,
25}
26
27/// Aggregate disk-usage report for `zlayer system df`.
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, ToSchema)]
29pub struct SystemDiskUsage {
30    pub categories: Vec<DiskUsageCategory>,
31    /// Sum of category `total_bytes` (de-duplicated: shared lower layers counted once).
32    pub total_bytes: u64,
33    /// Sum of category `reclaimable_bytes`.
34    pub total_reclaimable_bytes: u64,
35}
36
37/// Summary of what `zlayer system prune` removed, per category + totals.
38#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default, ToSchema)]
39pub struct SystemPruneReport {
40    /// Per-category removed-item identifiers (container ids, image refs, volume
41    /// names, layer digests, deployment names, ...).
42    pub stopped_containers: Vec<String>,
43    pub dead_deployments: Vec<String>,
44    pub deleted_images: Vec<String>,
45    pub deleted_volumes: Vec<String>,
46    pub deleted_networks: Vec<String>,
47    pub reclaimed_layers: Vec<String>,
48    pub reclaimed_blobs: Vec<String>,
49    pub reclaimed_bundles: Vec<String>,
50    /// Total bytes reclaimed across all categories.
51    pub space_reclaimed_bytes: u64,
52}
53
54impl SystemPruneReport {
55    /// Total number of removed items across all categories.
56    #[must_use]
57    pub fn total_items(&self) -> usize {
58        self.stopped_containers.len()
59            + self.dead_deployments.len()
60            + self.deleted_images.len()
61            + self.deleted_volumes.len()
62            + self.deleted_networks.len()
63            + self.reclaimed_layers.len()
64            + self.reclaimed_blobs.len()
65            + self.reclaimed_bundles.len()
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72
73    #[test]
74    fn test_system_disk_usage_roundtrip() {
75        let usage = SystemDiskUsage {
76            categories: vec![
77                DiskUsageCategory {
78                    name: "images".to_string(),
79                    total_bytes: 10_000,
80                    reclaimable_bytes: 2_500,
81                    item_count: 7,
82                },
83                DiskUsageCategory {
84                    name: "layers".to_string(),
85                    total_bytes: 40_000,
86                    reclaimable_bytes: 5_000,
87                    item_count: 23,
88                },
89            ],
90            total_bytes: 50_000,
91            total_reclaimable_bytes: 7_500,
92        };
93
94        let json = serde_json::to_string(&usage).unwrap();
95        assert!(json.contains("\"images\""));
96        assert!(json.contains("\"total_bytes\":50000"));
97        assert!(json.contains("\"total_reclaimable_bytes\":7500"));
98
99        let back: SystemDiskUsage = serde_json::from_str(&json).unwrap();
100        assert_eq!(back, usage);
101    }
102
103    #[test]
104    fn test_system_disk_usage_default() {
105        let usage = SystemDiskUsage::default();
106        assert!(usage.categories.is_empty());
107        assert_eq!(usage.total_bytes, 0);
108        assert_eq!(usage.total_reclaimable_bytes, 0);
109    }
110
111    #[test]
112    fn test_system_prune_report_roundtrip() {
113        let report = SystemPruneReport {
114            stopped_containers: vec!["c1".to_string(), "c2".to_string()],
115            dead_deployments: vec!["dep-old".to_string()],
116            deleted_images: vec!["nginx:latest".to_string()],
117            deleted_volumes: vec!["vol-a".to_string()],
118            deleted_networks: vec![],
119            reclaimed_layers: vec!["sha256:aaa".to_string(), "sha256:bbb".to_string()],
120            reclaimed_blobs: vec!["sha256:ccc".to_string()],
121            reclaimed_bundles: vec!["bundle-1".to_string()],
122            space_reclaimed_bytes: 123_456,
123        };
124
125        let json = serde_json::to_string(&report).unwrap();
126        let back: SystemPruneReport = serde_json::from_str(&json).unwrap();
127        assert_eq!(back, report);
128        assert!(json.contains("\"space_reclaimed_bytes\":123456"));
129    }
130
131    #[test]
132    fn test_system_prune_report_total_items() {
133        let report = SystemPruneReport {
134            stopped_containers: vec!["c1".to_string(), "c2".to_string()],
135            dead_deployments: vec!["dep-old".to_string()],
136            deleted_images: vec!["nginx:latest".to_string()],
137            deleted_volumes: vec!["vol-a".to_string()],
138            deleted_networks: vec!["net-1".to_string()],
139            reclaimed_layers: vec!["sha256:aaa".to_string(), "sha256:bbb".to_string()],
140            reclaimed_blobs: vec!["sha256:ccc".to_string()],
141            reclaimed_bundles: vec!["bundle-1".to_string()],
142            space_reclaimed_bytes: 0,
143        };
144        // 2 + 1 + 1 + 1 + 1 + 2 + 1 + 1 = 10
145        assert_eq!(report.total_items(), 10);
146    }
147
148    #[test]
149    fn test_system_prune_report_default_empty() {
150        let report = SystemPruneReport::default();
151        assert_eq!(report.total_items(), 0);
152        assert_eq!(report.space_reclaimed_bytes, 0);
153    }
154}