1use std::sync::Arc;
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use soma_fleet::{HostId, HostRecord, TopologyRevision};
6use soma_ops::{MutationSendState, OperationId, OperationName, Timestamp, VerificationStatus};
7use tokio_util::sync::CancellationToken;
8
9use crate::{
10 ContainerInspect, ContainerReader, ContainerState, InfraError, InfraResult, MutationResult,
11 MutationVerification,
12};
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16pub struct ContainerRecreateFingerprint {
17 pub container: String,
19 pub name: String,
21 pub image: String,
23 pub state: ContainerState,
25 pub sha256: String,
27}
28
29impl ContainerRecreateFingerprint {
30 pub fn new(
32 container: impl Into<String>,
33 name: impl Into<String>,
34 image: impl Into<String>,
35 state: ContainerState,
36 sha256: impl Into<String>,
37 ) -> InfraResult<Self> {
38 let container = container.into();
39 let name = name.into();
40 let image = image.into();
41 let sha256 = sha256.into();
42 if container.is_empty() || name.is_empty() || image.is_empty() {
43 return Err(InfraError::InvalidRequest {
44 domain: "container-recreate",
45 message: "container, name, and image are required".into(),
46 });
47 }
48 if sha256.len() != 64 || !sha256.bytes().all(|byte| byte.is_ascii_hexdigit()) {
49 return Err(InfraError::InvalidRequest {
50 domain: "container-recreate",
51 message: "configuration fingerprint must be SHA-256 hex".into(),
52 });
53 }
54 Ok(Self {
55 container,
56 name,
57 image,
58 state,
59 sha256: sha256.to_ascii_lowercase(),
60 })
61 }
62}
63
64#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct ContainerRecreateRequest {
67 operation_id: OperationId,
68 operation: OperationName,
69 expected: ContainerRecreateFingerprint,
70 pull: bool,
71 deadline: Timestamp,
72}
73
74impl ContainerRecreateRequest {
75 #[must_use]
77 pub fn new(
78 operation_id: OperationId,
79 operation: OperationName,
80 expected: ContainerRecreateFingerprint,
81 pull: bool,
82 deadline: Timestamp,
83 ) -> Self {
84 Self {
85 operation_id,
86 operation,
87 expected,
88 pull,
89 deadline,
90 }
91 }
92 #[must_use]
94 pub fn operation_id(&self) -> &OperationId {
95 &self.operation_id
96 }
97 #[must_use]
99 pub fn operation(&self) -> &OperationName {
100 &self.operation
101 }
102 #[must_use]
104 pub const fn expected(&self) -> &ContainerRecreateFingerprint {
105 &self.expected
106 }
107 #[must_use]
109 pub const fn pull(&self) -> bool {
110 self.pull
111 }
112 #[must_use]
114 pub const fn deadline(&self) -> Timestamp {
115 self.deadline
116 }
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "snake_case")]
122pub enum ContainerRecreateStage {
123 Prepared,
125 Stopped,
127 Removed,
129 Created,
131 Started,
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
137pub struct ContainerRecreateReceipt {
138 pub host: HostId,
140 pub topology_revision: TopologyRevision,
142 pub original_container: String,
144 pub new_container: Option<String>,
146 pub name: String,
148 pub image: String,
150 pub stage: ContainerRecreateStage,
152 pub send_state: MutationSendState,
154 pub pulled: bool,
156}
157
158#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
160pub struct ContainerRecreateOutcome {
161 pub host: HostId,
163 pub topology_revision: TopologyRevision,
165 pub before: ContainerInspect,
167 pub after: Option<ContainerInspect>,
169 pub original_container: String,
171 pub new_container: Option<String>,
173 pub changed: bool,
175 pub stage: ContainerRecreateStage,
177 pub pulled: bool,
179 pub send_state: MutationSendState,
181 pub verification_status: VerificationStatus,
183 pub verification: MutationVerification,
185}
186
187#[async_trait]
189pub trait ContainerRecreateInspector: Send + Sync {
190 async fn recreate_fingerprint(
192 &self,
193 host: &HostRecord,
194 container: &str,
195 cancellation: &CancellationToken,
196 ) -> InfraResult<ContainerRecreateFingerprint>;
197}
198
199#[async_trait]
201pub trait ContainerRecreateMutator: Send + Sync {
202 async fn recreate_container(
204 &self,
205 host: &HostRecord,
206 request: &ContainerRecreateRequest,
207 cancellation: &CancellationToken,
208 ) -> MutationResult<ContainerRecreateReceipt>;
209}
210
211pub trait ContainerRecreateClient:
213 ContainerReader + ContainerRecreateInspector + ContainerRecreateMutator
214{
215}
216impl<T> ContainerRecreateClient for T where
217 T: ContainerReader + ContainerRecreateInspector + ContainerRecreateMutator
218{
219}
220
221#[async_trait]
223pub trait ContainerRecreateClientProvider: Send + Sync {
224 async fn recreate_client(
226 &self,
227 host: &HostRecord,
228 cancellation: &CancellationToken,
229 ) -> InfraResult<Arc<dyn ContainerRecreateClient>>;
230}
231
232#[cfg(test)]
233#[path = "container_recreate_tests.rs"]
234mod tests;