Skip to main content

opendev_docker/
models.rs

1//! Data models for Docker runtime configuration and communication.
2//!
3//! Ports the Python `models.py` plus configuration types from `deployment.py`.
4
5use serde::{Deserialize, Serialize};
6
7// =============================================================================
8// Configuration
9// =============================================================================
10
11/// Whether to interact with a local or remote Docker daemon.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13#[serde(rename_all = "lowercase")]
14#[derive(Default)]
15pub enum RuntimeType {
16    #[default]
17    Local,
18    Remote,
19}
20
21/// A single volume mount specification.
22#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
23pub struct VolumeMount {
24    /// Host path.
25    pub host_path: String,
26    /// Container path.
27    pub container_path: String,
28    /// Read-only flag.
29    #[serde(default)]
30    pub read_only: bool,
31}
32
33/// A single port mapping.
34#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
35pub struct PortMapping {
36    /// Port on the host.
37    pub host_port: u16,
38    /// Port inside the container.
39    pub container_port: u16,
40    /// Protocol (tcp/udp).
41    #[serde(default = "default_protocol")]
42    pub protocol: String,
43}
44
45fn default_protocol() -> String {
46    "tcp".into()
47}
48
49/// Status of a Docker container.
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
51#[serde(rename_all = "lowercase")]
52#[derive(Default)]
53pub enum ContainerStatus {
54    Created,
55    Running,
56    Paused,
57    Stopped,
58    Removing,
59    #[default]
60    Unknown,
61}
62
63/// Specification for creating a container (image, resources, mounts, …).
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct ContainerSpec {
66    /// Docker image name (e.g. `python:3.11`).
67    pub image: String,
68    /// Memory limit (e.g. `4g`).
69    #[serde(default = "default_memory")]
70    pub memory: String,
71    /// CPU limit (e.g. `4`).
72    #[serde(default = "default_cpus")]
73    pub cpus: String,
74    /// Docker network mode.
75    #[serde(default = "default_network")]
76    pub network_mode: String,
77    /// Volume mounts.
78    #[serde(default)]
79    pub volumes: Vec<VolumeMount>,
80    /// Port mappings.
81    #[serde(default)]
82    pub ports: Vec<PortMapping>,
83    /// Extra environment variables.
84    #[serde(default)]
85    pub environment: std::collections::HashMap<String, String>,
86    /// Entrypoint command override.
87    pub entrypoint: Option<String>,
88    /// Command to run.
89    pub command: Option<Vec<String>>,
90}
91
92fn default_memory() -> String {
93    "4g".into()
94}
95fn default_cpus() -> String {
96    "4".into()
97}
98fn default_network() -> String {
99    "bridge".into()
100}
101
102/// Top-level Docker configuration (mirrors Python `DockerConfig`).
103#[derive(Debug, Clone, Serialize, Deserialize)]
104pub struct DockerConfig {
105    /// Container image.
106    #[serde(default = "default_image")]
107    pub image: String,
108    /// Memory limit.
109    #[serde(default = "default_memory")]
110    pub memory: String,
111    /// CPU limit.
112    #[serde(default = "default_cpus")]
113    pub cpus: String,
114    /// Docker network mode.
115    #[serde(default = "default_network")]
116    pub network_mode: String,
117    /// Maximum seconds to wait for the container to become ready.
118    #[serde(default = "default_startup_timeout")]
119    pub startup_timeout: f64,
120    /// Image pull policy: `always`, `never`, or `if-not-present`.
121    #[serde(default = "default_pull_policy")]
122    pub pull_policy: String,
123    /// Port the server listens on inside the container.
124    #[serde(default = "default_server_port")]
125    pub server_port: u16,
126    /// Extra environment variables.
127    #[serde(default)]
128    pub environment: std::collections::HashMap<String, String>,
129    /// Shell init command prepended to every command.
130    #[serde(default)]
131    pub shell_init: String,
132    /// Volume mounts.
133    #[serde(default)]
134    pub volumes: Vec<VolumeMount>,
135    /// Runtime type (local or remote Docker daemon).
136    #[serde(default)]
137    pub runtime_type: RuntimeType,
138    /// Remote host for SSH-based Docker access.
139    pub remote_host: Option<String>,
140    /// SSH user for remote Docker.
141    pub remote_user: Option<String>,
142    /// SSH key path.
143    pub ssh_key_path: Option<String>,
144}
145
146fn default_image() -> String {
147    "python:3.11".into()
148}
149fn default_startup_timeout() -> f64 {
150    120.0
151}
152fn default_pull_policy() -> String {
153    "if-not-present".into()
154}
155fn default_server_port() -> u16 {
156    8000
157}
158
159impl Default for DockerConfig {
160    fn default() -> Self {
161        Self {
162            image: default_image(),
163            memory: default_memory(),
164            cpus: default_cpus(),
165            network_mode: default_network(),
166            startup_timeout: default_startup_timeout(),
167            pull_policy: default_pull_policy(),
168            server_port: default_server_port(),
169            environment: Default::default(),
170            shell_init: String::new(),
171            volumes: Vec::new(),
172            runtime_type: RuntimeType::default(),
173            remote_host: None,
174            remote_user: None,
175            ssh_key_path: None,
176        }
177    }
178}
179
180// =============================================================================
181// Session management request/response
182// =============================================================================
183
184/// Request to create a new bash session.
185#[derive(Debug, Clone, Serialize, Deserialize)]
186pub struct CreateSessionRequest {
187    #[serde(default = "default_session_name")]
188    pub session: String,
189    #[serde(default = "default_startup_timeout_session")]
190    pub startup_timeout: f64,
191}
192
193fn default_session_name() -> String {
194    "default".into()
195}
196fn default_startup_timeout_session() -> f64 {
197    10.0
198}
199
200impl Default for CreateSessionRequest {
201    fn default() -> Self {
202        Self {
203            session: default_session_name(),
204            startup_timeout: default_startup_timeout_session(),
205        }
206    }
207}
208
209/// Response after creating a bash session.
210#[derive(Debug, Clone, Serialize, Deserialize)]
211pub struct CreateSessionResponse {
212    pub success: bool,
213    pub session: String,
214    #[serde(default)]
215    pub message: String,
216}
217
218/// Request to close a bash session.
219#[derive(Debug, Clone, Serialize, Deserialize)]
220pub struct CloseSessionRequest {
221    #[serde(default = "default_session_name")]
222    pub session: String,
223}
224
225impl Default for CloseSessionRequest {
226    fn default() -> Self {
227        Self {
228            session: default_session_name(),
229        }
230    }
231}
232
233/// Response after closing a bash session.
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct CloseSessionResponse {
236    pub success: bool,
237    #[serde(default)]
238    pub message: String,
239}
240
241// =============================================================================
242// Command execution
243// =============================================================================
244
245/// How to handle non-zero exit codes.
246#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
247#[serde(rename_all = "lowercase")]
248#[derive(Default)]
249pub enum CheckMode {
250    /// Raise an error on non-zero exit.
251    Raise,
252    /// Return the result silently.
253    #[default]
254    Silent,
255    /// Skip exit-code checking entirely.
256    Ignore,
257}
258
259/// Action to execute in a bash session.
260#[derive(Debug, Clone, Serialize, Deserialize)]
261pub struct BashAction {
262    pub command: String,
263    #[serde(default = "default_session_name")]
264    pub session: String,
265    #[serde(default = "default_timeout")]
266    pub timeout: f64,
267    #[serde(default)]
268    pub check: CheckMode,
269}
270
271fn default_timeout() -> f64 {
272    120.0
273}
274
275/// Observation/result from executing a bash action.
276#[derive(Debug, Clone, Serialize, Deserialize)]
277pub struct BashObservation {
278    #[serde(default)]
279    pub output: String,
280    pub exit_code: Option<i32>,
281    pub failure_reason: Option<String>,
282}
283
284// =============================================================================
285// File operations
286// =============================================================================
287
288/// Request to read a file from the container.
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct ReadFileRequest {
291    pub path: String,
292}
293
294/// Response with file contents.
295#[derive(Debug, Clone, Serialize, Deserialize)]
296pub struct ReadFileResponse {
297    pub success: bool,
298    #[serde(default)]
299    pub content: String,
300    pub error: Option<String>,
301}
302
303/// Request to write a file in the container.
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub struct WriteFileRequest {
306    pub path: String,
307    pub content: String,
308}
309
310/// Response after writing a file.
311#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct WriteFileResponse {
313    pub success: bool,
314    pub error: Option<String>,
315}
316
317// =============================================================================
318// Health check
319// =============================================================================
320
321/// Health check response.
322#[derive(Debug, Clone, Serialize, Deserialize)]
323pub struct IsAliveResponse {
324    #[serde(default = "default_status_ok")]
325    pub status: String,
326    #[serde(default)]
327    pub message: String,
328}
329
330fn default_status_ok() -> String {
331    "ok".into()
332}
333
334impl Default for IsAliveResponse {
335    fn default() -> Self {
336        Self {
337            status: default_status_ok(),
338            message: String::new(),
339        }
340    }
341}
342
343/// Serialized exception for transfer over HTTP.
344#[derive(Debug, Clone, Serialize, Deserialize)]
345pub struct ExceptionTransfer {
346    pub message: String,
347    pub class_name: String,
348    pub module: String,
349    #[serde(default)]
350    pub traceback: String,
351    #[serde(default)]
352    pub extra: std::collections::HashMap<String, serde_json::Value>,
353}
354
355// =============================================================================
356// Tool handler result
357// =============================================================================
358
359/// Generic result from a Docker tool handler operation.
360#[derive(Debug, Clone, Serialize, Deserialize)]
361pub struct ToolResult {
362    pub success: bool,
363    pub output: Option<String>,
364    pub error: Option<String>,
365    pub exit_code: Option<i32>,
366}
367
368#[cfg(test)]
369#[path = "models_tests.rs"]
370mod tests;