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