1#![warn(missing_docs)]
2
3use std::sync::Arc;
6use std::time::SystemTime;
7use tokio::sync::RwLock;
8use uuid::Uuid;
9
10use docker_types::{ContainerConfig, ContainerInfo, ContainerStatus, DockerConfig, DockerError};
11
12pub type Result<T> = std::result::Result<T, DockerError>;
14
15pub struct ContainerRuntime {
17 config: Arc<DockerConfig>,
19 containers: Arc<RwLock<Vec<ContainerInfo>>>,
21}
22
23impl ContainerRuntime {
24 pub fn new() -> Result<Self> {
26 let default_config = DockerConfig {
28 data_dir: "./data".to_string(),
29 image_dir: "./data/images".to_string(),
30 container_dir: "./data/containers".to_string(),
31 network_dir: "./data/networks".to_string(),
32 default_network: "default".to_string(),
33 default_resources: docker_types::ResourceLimits {
34 cpu_limit: 1.0,
35 memory_limit: 512,
36 storage_limit: 10,
37 network_limit: 10,
38 },
39 log_config: docker_types::LogConfig {
40 log_level: "info".to_string(),
41 log_file: "./data/logs/docker.log".to_string(),
42 max_log_size: 100,
43 },
44 };
45
46 Ok(Self {
47 config: Arc::new(default_config),
48 containers: Arc::new(RwLock::new(vec![])),
49 })
50 }
51
52 pub fn new_with_config(config: Arc<DockerConfig>) -> Result<Self> {
54 Ok(Self {
55 config,
56 containers: Arc::new(RwLock::new(vec![])),
57 })
58 }
59
60 pub fn get_config(&self) -> Arc<DockerConfig> {
62 self.config.clone()
63 }
64
65 pub async fn start(&self) {
67 tokio::spawn(async move {
69 loop {
71 tokio::time::sleep(tokio::time::Duration::from_mins(5)).await;
72 }
74 });
75 }
76
77 pub async fn create_container(&self, config: ContainerConfig) -> Result<ContainerInfo> {
79 let container_id = Uuid::new_v4().to_string();
81
82 let container_info = ContainerInfo {
84 id: container_id.clone(),
85 name: config.name.clone(),
86 image: config.image.clone(),
87 status: ContainerStatus::Creating,
88 config,
89 created_at: SystemTime::now(),
90 started_at: None,
91 stopped_at: None,
92 pid: None,
93 network_info: docker_types::NetworkInfo {
94 ip_address: None,
95 ports: Default::default(),
96 network_name: self.config.default_network.clone(),
97 },
98 };
99
100 let mut containers = self.containers.write().await;
102 containers.push(container_info.clone());
103
104 Ok(container_info)
105 }
106
107 pub async fn start_container(&self, container_id: &str) -> Result<()> {
109 let mut containers = self.containers.write().await;
110
111 if let Some(container) = containers.iter_mut().find(|c| c.id == container_id) {
112 if container.status != ContainerStatus::Creating
114 && container.status != ContainerStatus::Stopped
115 {
116 return Err(DockerError::container_error(
117 "Container is not in a state to start".to_string(),
118 ));
119 }
120
121 container.status = ContainerStatus::Running;
130 container.started_at = Some(SystemTime::now());
131 container.pid = Some(12345); Ok(())
134 } else {
135 Err(DockerError::not_found(
136 "container",
137 format!("Container {} not found", container_id),
138 ))
139 }
140 }
141
142 pub async fn stop_container(&self, container_id: &str) -> Result<()> {
144 let mut containers = self.containers.write().await;
145
146 if let Some(container) = containers.iter_mut().find(|c| c.id == container_id) {
147 if container.status != ContainerStatus::Running {
149 return Err(DockerError::container_error(
150 "Container is not in a running state".to_string(),
151 ));
152 }
153
154 container.status = ContainerStatus::Stopped;
156 container.stopped_at = Some(SystemTime::now());
157 container.pid = None;
158
159 Ok(())
160 } else {
161 Err(DockerError::not_found(
162 "container",
163 format!("Container {} not found", container_id),
164 ))
165 }
166 }
167
168 pub async fn run_container(
170 &self,
171 image: String,
172 name: Option<String>,
173 _ports: Vec<String>,
174 ) -> Result<ContainerInfo> {
175 let config = ContainerConfig {
177 name: name.unwrap_or_else(|| {
178 format!(
179 "container-{}",
180 Uuid::new_v4().to_string().split('-').next().unwrap()
181 )
182 }),
183 image,
184 command: vec!["/bin/sh".to_string()],
185 environment: Default::default(),
186 ports: Default::default(),
187 volumes: Default::default(),
188 resources: self.config.default_resources.clone(),
189 network: docker_types::NetworkConfig {
190 network_name: self.config.default_network.clone(),
191 static_ip: None,
192 hostname: None,
193 aliases: None,
194 network_mode: None,
195 enable_ipv6: false,
196 },
197 restart_policy: None,
198 healthcheck: None,
199 deploy: None,
200 };
201
202 let container_info = self.create_container(config).await?;
204 self.start_container(&container_info.id).await?;
205
206 Ok(container_info)
207 }
208
209 pub async fn list_containers(&self, all: bool) -> Result<Vec<ContainerInfo>> {
211 let containers = self.containers.read().await;
212
213 if all {
214 Ok(containers.clone())
215 } else {
216 Ok(containers
217 .iter()
218 .filter(|c| c.status == ContainerStatus::Running)
219 .cloned()
220 .collect())
221 }
222 }
223
224 pub async fn remove_container(&self, container_id: &str) -> Result<()> {
226 let mut containers = self.containers.write().await;
227
228 if let Some(index) = containers.iter().position(|c| c.id == container_id) {
229 let container = &containers[index];
231 if container.status == ContainerStatus::Running {
232 return Err(DockerError::container_error(
233 "Cannot remove a running container".to_string(),
234 ));
235 }
236
237 containers.remove(index);
239
240 Ok(())
241 } else {
242 Err(DockerError::not_found(
243 "container",
244 format!("Container {} not found", container_id),
245 ))
246 }
247 }
248}