Skip to main content

soma_infra/
compose_down.rs

1use async_trait::async_trait;
2use serde::{Deserialize, Serialize};
3use soma_fleet::{HostId, HostRecord, TopologyRevision};
4use soma_ops::{MutationSendState, OperationId, OperationName, Timestamp, VerificationStatus};
5use tokio_util::sync::CancellationToken;
6
7use crate::{
8    ComposeInspector, ComposeProjectRef, ComposeRecreateFingerprint, ComposeStatus, InfraError,
9    InfraResult, MutationResult, MutationVerification,
10};
11
12/// Deadline-bound Compose teardown request.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct ComposeDownRequest {
15    operation_id: OperationId,
16    operation: OperationName,
17    project: ComposeProjectRef,
18    expected: ComposeRecreateFingerprint,
19    force: bool,
20    remove_volumes: bool,
21    deadline: Timestamp,
22}
23
24impl ComposeDownRequest {
25    /// Creates a validated teardown request.
26    pub fn new(
27        operation_id: OperationId,
28        operation: OperationName,
29        project: ComposeProjectRef,
30        expected: ComposeRecreateFingerprint,
31        force: bool,
32        remove_volumes: bool,
33        deadline: Timestamp,
34    ) -> InfraResult<Self> {
35        if remove_volumes && !force {
36            return Err(InfraError::InvalidRequest {
37                domain: "compose-down",
38                message: "remove_volumes=true requires force=true".into(),
39            });
40        }
41        Ok(Self {
42            operation_id,
43            operation,
44            project,
45            expected,
46            force,
47            remove_volumes,
48            deadline,
49        })
50    }
51
52    /// Returns the operation identity.
53    #[must_use]
54    pub fn operation_id(&self) -> &OperationId {
55        &self.operation_id
56    }
57    /// Returns the canonical operation.
58    #[must_use]
59    pub fn operation(&self) -> &OperationName {
60        &self.operation
61    }
62    /// Returns the Compose project reference.
63    #[must_use]
64    pub const fn project(&self) -> &ComposeProjectRef {
65        &self.project
66    }
67    /// Returns the expected pre-state.
68    #[must_use]
69    pub const fn expected(&self) -> &ComposeRecreateFingerprint {
70        &self.expected
71    }
72    /// Returns explicit force confirmation.
73    #[must_use]
74    pub const fn force(&self) -> bool {
75        self.force
76    }
77    /// Returns whether named volumes are removed.
78    #[must_use]
79    pub const fn remove_volumes(&self) -> bool {
80        self.remove_volumes
81    }
82    /// Returns the execution deadline.
83    #[must_use]
84    pub const fn deadline(&self) -> Timestamp {
85        self.deadline
86    }
87}
88
89/// Process driver receipt for Compose down.
90#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
91pub struct ComposeDownReceipt {
92    /// Target host.
93    pub host: HostId,
94    /// Exact topology revision.
95    pub topology_revision: TopologyRevision,
96    /// Project name.
97    pub project: String,
98    /// Whether volume deletion was requested.
99    pub remove_volumes: bool,
100    /// Backend send state.
101    pub send_state: MutationSendState,
102    /// Bounded stdout.
103    pub stdout: String,
104    /// Bounded stderr.
105    pub stderr: String,
106    /// Whether output was truncated.
107    pub output_truncated: bool,
108}
109
110/// Verified Compose teardown outcome.
111#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
112pub struct ComposeDownOutcome {
113    /// Target host.
114    pub host: HostId,
115    /// Exact topology revision.
116    pub topology_revision: TopologyRevision,
117    /// Project name.
118    pub project: String,
119    /// Service state before teardown.
120    pub before: ComposeStatus,
121    /// Service state after teardown.
122    pub after: ComposeStatus,
123    /// Whether a nonempty service set or volume deletion was requested.
124    pub changed: bool,
125    /// Backend receipt.
126    pub receipt: ComposeDownReceipt,
127    /// Verification status.
128    pub verification_status: VerificationStatus,
129    /// Verification explanation.
130    pub verification: MutationVerification,
131}
132
133/// Executes Docker Compose teardown.
134#[async_trait]
135pub trait ComposeDownMutator: Send + Sync {
136    /// Performs one shell-free Compose down command.
137    async fn down_compose(
138        &self,
139        host: &HostRecord,
140        request: &ComposeDownRequest,
141        cancellation: &CancellationToken,
142    ) -> MutationResult<ComposeDownReceipt>;
143}
144
145/// Complete Compose client required by teardown verification.
146pub trait ComposeDownClient: ComposeInspector + ComposeDownMutator {}
147impl<T> ComposeDownClient for T where T: ComposeInspector + ComposeDownMutator {}
148
149#[cfg(test)]
150#[path = "compose_down_tests.rs"]
151mod tests;