Skip to main content

zlayer_storage/
types.rs

1//! Core types for layer storage
2
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7/// Identifies a container's persistent layer state
8#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
9pub struct ContainerLayerId {
10    /// Service/deployment name
11    pub service: String,
12    /// Unique container instance ID
13    pub instance_id: String,
14}
15
16impl ContainerLayerId {
17    pub fn new(service: impl Into<String>, instance_id: impl Into<String>) -> Self {
18        Self {
19            service: service.into(),
20            instance_id: instance_id.into(),
21        }
22    }
23
24    #[must_use]
25    pub fn to_key(&self) -> String {
26        format!("{}_{}", self.service, self.instance_id)
27    }
28}
29
30impl std::fmt::Display for ContainerLayerId {
31    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32        write!(f, "{}/{}", self.service, self.instance_id)
33    }
34}
35
36/// Metadata for a persisted layer snapshot
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub struct LayerSnapshot {
39    /// SHA256 digest of the tarball (content-addressed key)
40    pub digest: String,
41    /// Size in bytes (uncompressed)
42    pub size_bytes: u64,
43    /// Size in bytes (compressed with zstd)
44    pub compressed_size_bytes: u64,
45    /// Timestamp when snapshot was created
46    pub created_at: chrono::DateTime<chrono::Utc>,
47    /// Number of files in the layer
48    pub file_count: u64,
49}
50
51/// Tracks sync state between local and S3
52#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct SyncState {
54    /// Container this state belongs to
55    pub container_id: ContainerLayerId,
56    /// Current local layer digest (None if no changes)
57    pub local_digest: Option<String>,
58    /// Last successfully uploaded digest
59    pub remote_digest: Option<String>,
60    /// Timestamp of last successful sync
61    pub last_sync: Option<chrono::DateTime<chrono::Utc>>,
62    /// In-progress upload state (for crash recovery)
63    pub pending_upload: Option<PendingUpload>,
64}
65
66impl SyncState {
67    #[must_use]
68    pub fn new(container_id: ContainerLayerId) -> Self {
69        Self {
70            container_id,
71            local_digest: None,
72            remote_digest: None,
73            last_sync: None,
74            pending_upload: None,
75        }
76    }
77
78    #[must_use]
79    pub fn needs_upload(&self) -> bool {
80        match (&self.local_digest, &self.remote_digest) {
81            (Some(local), Some(remote)) => local != remote,
82            (Some(_), None) => true,
83            _ => false,
84        }
85    }
86}
87
88/// State for resumable multipart uploads
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct PendingUpload {
91    /// S3 multipart upload ID
92    pub upload_id: String,
93    /// S3 object key
94    pub object_key: String,
95    /// Total parts expected
96    pub total_parts: u32,
97    /// Parts successfully uploaded (`part_number` -> `ETag`)
98    pub completed_parts: HashMap<u32, CompletedPart>,
99    /// Part size in bytes
100    pub part_size: u64,
101    /// Local file path of tarball being uploaded
102    pub local_tarball_path: PathBuf,
103    /// Started at timestamp
104    pub started_at: chrono::DateTime<chrono::Utc>,
105    /// Digest of the layer being uploaded
106    pub digest: String,
107}
108
109impl PendingUpload {
110    #[must_use]
111    pub fn missing_parts(&self) -> Vec<u32> {
112        (1..=self.total_parts)
113            .filter(|p| !self.completed_parts.contains_key(p))
114            .collect()
115    }
116
117    #[must_use]
118    pub fn is_complete(&self) -> bool {
119        self.completed_parts.len() == self.total_parts as usize
120    }
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct CompletedPart {
125    pub part_number: u32,
126    pub e_tag: String,
127}