1use std::{fmt::Display, str::FromStr, time::Duration};
10
11use lazy_static::lazy_static;
12use rand::{distributions::Alphanumeric, Rng};
13use regex::Regex;
14
15use crate::error::{ContainerResult, ContainersError};
16
17lazy_static! {
18 static ref IMAGE_REGEX: Regex = Regex::new("([0-9a-zA-Z./]+)(:([0-9a-zA-Z.]+))?").unwrap();
19}
20
21pub trait TryIntoContainer {
22 fn try_into_container(self) -> ContainerResult<Container>;
23}
24
25pub trait IntoContainer {
26 fn into_container(self) -> Container;
27}
28
29impl<T: TryIntoContainer> IntoContainer for T {
30 fn into_container(self) -> Container {
31 self.try_into_container().unwrap()
32 }
33}
34
35impl IntoContainer for Container {
36 fn into_container(self) -> Container {
37 self
38 }
39}
40
41#[derive(Clone)]
42pub struct HealthCheck {
43 pub command: String,
44 pub retries: Option<u32>,
45 pub interval: Option<Duration>,
46 pub start_period: Option<Duration>,
47 pub timeout: Option<Duration>,
48}
49
50impl HealthCheck {
51 pub fn new(command: &str) -> Self {
52 Self {
53 command: command.into(),
54 retries: None,
55 interval: None,
56 start_period: None,
57 timeout: None,
58 }
59 }
60
61 pub fn retries(mut self, retries: u32) -> Self {
62 self.retries = Some(retries);
63 self
64 }
65
66 pub fn interval(mut self, interval: Duration) -> Self {
67 self.interval = Some(interval);
68 self
69 }
70
71 pub fn start_period(mut self, start_period: Duration) -> Self {
72 self.start_period = Some(start_period);
73 self
74 }
75
76 pub fn timeout(mut self, timeout: Duration) -> Self {
77 self.timeout = Some(timeout);
78 self
79 }
80}
81
82#[derive(Clone, Debug)]
86pub enum WaitStrategy {
87 LogMessage { pattern: Regex },
91 HealthCheck,
95 WaitTime { duration: Duration },
99}
100
101#[derive(Clone)]
102pub struct Network {
103 }
105
106#[derive(Clone, PartialEq, Eq)]
107pub struct Port {
108 pub number: String,
109}
110
111impl<T> From<T> for Port
112where
113 T: ToString,
114{
115 fn from(value: T) -> Self {
116 Self {
117 number: value.to_string(),
118 }
119 }
120}
121
122#[derive(Clone)]
123pub struct PortMapping {
124 pub source: Port,
125 pub target: Port,
126}
127
128#[derive(Clone)]
129pub struct EnvVar {
130 pub key: String,
131 pub value: String,
132}
133
134impl EnvVar {
135 pub fn new(key: String, value: String) -> Self {
136 Self { key, value }
137 }
138}
139
140impl<K, V> From<(K, V)> for EnvVar
141where
142 K: Into<String>,
143 V: Into<String>,
144{
145 fn from(value: (K, V)) -> Self {
146 EnvVar::new(value.0.into(), value.1.into())
147 }
148}
149
150#[derive(Clone)]
151pub struct Image {
152 pub name: String,
153 pub tag: String,
154}
155
156impl Display for Image {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 write!(f, "{}:{}", self.name, self.tag)
159 }
160}
161
162impl Image {
163 pub fn from_name_and_tag(name: &str, tag: &str) -> Self {
164 Image {
165 name: name.to_string(),
166 tag: tag.to_string(),
167 }
168 }
169}
170
171impl FromStr for Image {
172 type Err = ContainersError;
173
174 fn from_str(s: &str) -> Result<Self, Self::Err> {
175 let caps = IMAGE_REGEX.captures(s);
176
177 if let Some(cap) = caps {
178 Ok(Self::from_name_and_tag(
179 cap.get(1).unwrap().as_str(),
180 cap.get(3).map(|m| m.as_str()).unwrap_or("latest"),
181 ))
182 } else {
183 Err(ContainersError::InvalidImageName {
184 name: s.to_string(),
185 })
186 }
187 }
188}
189
190impl From<Image> for String {
191 fn from(i: Image) -> Self {
192 format!("{}:{}", i.name, i.tag)
193 }
194}
195
196impl From<&Image> for String {
197 fn from(i: &Image) -> Self {
198 format!("{}:{}", i.name, i.tag)
199 }
200}
201
202#[derive(Clone)]
203pub enum Volume {
204 Mount {
205 host_path: String,
206 mount_point: String,
207 },
208 Named {
209 name: String,
210 mount_point: String,
211 },
212}
213
214#[derive(Clone)]
220pub struct Container {
221 pub name: String,
222 pub image: Image,
223 pub command: Vec<String>,
224 pub network: Option<Network>,
225 pub volumes: Vec<Volume>,
226 pub port_mappings: Vec<PortMapping>,
227 pub env_vars: Vec<EnvVar>,
228 pub health_check: Option<HealthCheck>,
229 pub wait_strategy: Option<WaitStrategy>,
230 pub additional_wait_period: Duration,
231}
232
233impl Container {
234 fn gen_hash() -> String {
235 rand::thread_rng()
236 .sample_iter(&Alphanumeric)
237 .take(8)
238 .map(char::from)
239 .collect()
240 }
241
242 pub fn from_image(image: Image) -> Self {
246 Container {
247 name: format!("contain-rs-{}", Self::gen_hash()),
248 image,
249 command: Vec::new(),
250 network: None,
251 port_mappings: Vec::new(),
252 env_vars: Vec::new(),
253 volumes: Vec::new(),
254 health_check: None,
255 wait_strategy: None,
256 additional_wait_period: Duration::from_secs(0),
257 }
258 }
259
260 pub fn name(&mut self, name: &str) -> &mut Self {
266 self.name = name.into();
267 self
268 }
269
270 pub fn command(&mut self, command: Vec<String>) -> &mut Self {
274 self.command = command;
275 self
276 }
277
278 pub fn arg<T: Into<String>>(&mut self, arg: T) -> &mut Self {
279 self.command.push(arg.into());
280 self
281 }
282
283 pub fn map_ports<T, T2>(&mut self, ports: &[(T, T2)]) -> &mut Self
284 where
285 T: Into<Port> + Clone,
286 T2: Into<Port> + Clone,
287 {
288 self.port_mappings = ports
289 .iter()
290 .cloned()
291 .map(|mapping| PortMapping {
292 source: mapping.0.into(),
293 target: mapping.1.into(),
294 })
295 .collect();
296
297 self
298 }
299
300 pub fn volume(&mut self, name: &str, mount_point: &str) -> &mut Self {
301 self.volumes.push(Volume::Named {
302 name: name.to_string(),
303 mount_point: mount_point.to_string(),
304 });
305
306 self
307 }
308
309 pub fn mount(&mut self, host_path: &str, mount_point: &str) -> &mut Self {
310 self.volumes.push(Volume::Mount {
311 host_path: host_path.to_string(),
312 mount_point: mount_point.to_string(),
313 });
314
315 self
316 }
317
318 pub fn map_port(&mut self, source: impl Into<Port>, target: impl Into<Port>) -> &mut Self {
322 self.port_mappings.push(PortMapping {
323 source: source.into(),
324 target: target.into(),
325 });
326 self
327 }
328
329 pub fn env_var<T: Into<String>>(&mut self, name: T, value: T) -> &mut Self {
333 self.env_vars.push((name, value).into());
334 self
335 }
336
337 pub fn wait_for(&mut self, strategy: WaitStrategy) -> &mut Self {
341 self.wait_strategy = Some(strategy);
342 self
343 }
344
345 pub fn additional_wait_period(&mut self, period: Duration) -> &mut Self {
351 self.additional_wait_period = period;
352 self
353 }
354
355 pub fn health_check(&mut self, health_check: HealthCheck) -> &mut Self {
361 self.health_check = Some(health_check);
362 self
363 }
364}
365
366impl From<Image> for Container {
367 fn from(image: Image) -> Self {
368 Container::from_image(image)
369 }
370}