1use std::collections::HashMap;
2
3use bollard::container::{
4 APIContainers, Config, CreateContainerOptions, HostConfig, ListContainersOptions, PortBinding,
5 PruneContainersOptions, RemoveContainerOptions, StartContainerOptions, StopContainerOptions,
6};
7use bollard::image::{
8 APIImages, BuildImageOptions, ListImagesOptions, PruneImagesOptions, RemoveImageOptions,
9};
10use bollard::Docker;
11use failure::Error;
12use futures::{Future, Stream};
13use hyper::client::connect::Connect;
14
15use crate::prelude::*;
16use crate::problem::Problem;
17use crate::{Environment, Printer, VERSION};
18
19const LABEL_KEY_VERSION: &str = "soma.version";
20const LABEL_KEY_USERNAME: &str = "soma.username";
21const LABEL_KEY_REPOSITORY: &str = "soma.repository";
22const LABEL_KEY_PROBLEM: &str = "soma.problem";
23
24type DockerLabel<'a> = HashMap<&'a str, &'a str>;
25type SomaFilter = HashMap<String, Vec<String>>;
26
27#[cfg(windows)]
28pub fn connect_default() -> SomaResult<Docker<impl Connect>> {
29 Docker::connect_with_named_pipe("npipe:////./pipe/docker_engine", 600)
30}
31
32#[cfg(unix)]
33pub fn connect_default() -> SomaResult<Docker<impl Connect>> {
34 Docker::connect_with_unix("unix:///var/run/docker.sock", 600)
35}
36
37#[derive(Clone, Copy, Debug)]
38pub enum VersionStatus {
39 Normal,
40 VersionMismatch,
41 NoVersionFound,
42}
43
44#[derive(Debug)]
45pub struct SomaImage {
46 repo_name: String,
47 prob_name: String,
48 image: APIImages,
49 status: VersionStatus,
50}
51
52impl SomaImage {
53 pub fn new(
54 repo_name: String,
55 prob_name: String,
56 image: APIImages,
57 status: VersionStatus,
58 ) -> SomaImage {
59 SomaImage {
60 repo_name,
61 prob_name,
62 image,
63 status,
64 }
65 }
66
67 pub fn repo_name(&self) -> &String {
68 &self.repo_name
69 }
70
71 pub fn prob_name(&self) -> &String {
72 &self.prob_name
73 }
74
75 pub fn image(&self) -> &APIImages {
76 &self.image
77 }
78
79 pub fn status(&self) -> VersionStatus {
80 self.status
81 }
82}
83
84struct SomaFilterBuilder {
85 label_filter: Vec<String>,
86}
87
88impl SomaFilterBuilder {
89 fn new() -> SomaFilterBuilder {
90 SomaFilterBuilder {
91 label_filter: vec![],
92 }
93 }
94
95 fn append_filter(mut self, key: String, value: String) -> SomaFilterBuilder {
96 self.label_filter.push(format!("{}={}", key, value));
97 self
98 }
99
100 pub fn append_user(self, username: &str) -> SomaFilterBuilder {
101 self.append_filter(LABEL_KEY_USERNAME.to_owned(), username.to_owned())
102 }
103
104 pub fn append_prob(self, problem: &Problem) -> SomaFilterBuilder {
105 self.append_filter(
106 LABEL_KEY_REPOSITORY.to_owned(),
107 problem.repo_name().to_string(),
108 )
109 .append_filter(
110 LABEL_KEY_PROBLEM.to_owned(),
111 problem.prob_name().to_string(),
112 )
113 }
114
115 pub fn build(self) -> SomaFilter {
116 let mut filter = SomaFilter::new();
117 filter.insert("label".to_owned(), self.label_filter);
118 filter
119 }
120}
121
122pub fn image_exists(images: &[SomaImage], image_name: &str) -> bool {
123 images.iter().any(|image| match &image.image().repo_tags {
124 Some(tags) => tags
125 .iter()
126 .any(|tag| tag.starts_with(format!("{}:", image_name).as_str())),
127 None => false,
128 })
129}
130
131pub fn image_from_repo_exists(images: &[SomaImage], repo_name: &str) -> bool {
132 images.iter().any(|image| image.repo_name() == repo_name)
133}
134
135pub fn image_from_prob_exists(images: &[SomaImage], problem: &Problem) -> bool {
136 images.iter().any(|image| {
137 image.repo_name() == problem.repo_name() && image.prob_name() == problem.prob_name()
138 })
139}
140
141pub fn image_from_repo_and_prob_exists(
142 images: &[SomaImage],
143 repo_name: &str,
144 prob_name: &str,
145) -> bool {
146 images
147 .iter()
148 .any(|image| image.repo_name() == repo_name && image.prob_name() == prob_name)
149}
150
151#[derive(Debug)]
152pub struct SomaContainer {
153 repo_name: String,
154 prob_name: String,
155 container: APIContainers,
156 status: VersionStatus,
157}
158
159impl SomaContainer {
160 pub fn new(
161 repo_name: String,
162 prob_name: String,
163 container: APIContainers,
164 status: VersionStatus,
165 ) -> SomaContainer {
166 SomaContainer {
167 repo_name,
168 prob_name,
169 container,
170 status,
171 }
172 }
173
174 pub fn repo_name(&self) -> &String {
175 &self.repo_name
176 }
177
178 pub fn prob_name(&self) -> &String {
179 &self.prob_name
180 }
181
182 pub fn container(&self) -> &APIContainers {
183 &self.container
184 }
185
186 pub fn status(&self) -> VersionStatus {
187 self.status
188 }
189}
190
191pub fn container_exists(containers: &[SomaContainer], container_id: &str) -> bool {
192 containers
193 .iter()
194 .any(|container| container.container().id == container_id)
195}
196
197pub fn container_from_prob_exists(containers: &[SomaContainer], problem: &Problem) -> bool {
198 containers.iter().any(|container| {
199 container.repo_name() == problem.repo_name() && container.prob_name() == problem.prob_name()
200 })
201}
202
203pub fn container_from_prob_running(containers: &[SomaContainer], problem: &Problem) -> bool {
204 containers.iter().any(|container| {
205 container.repo_name() == problem.repo_name()
206 && container.prob_name() == problem.prob_name()
207 && container.container().state == "running"
208 })
209}
210
211pub fn containers_from_prob(
212 containers: Vec<SomaContainer>,
213 problem: &Problem,
214) -> Vec<SomaContainer> {
215 containers
216 .into_iter()
217 .filter(|container| {
218 container.repo_name() == problem.repo_name()
219 && container.prob_name() == problem.prob_name()
220 })
221 .collect()
222}
223
224pub fn list_containers(
225 env: &Environment<impl Connect, impl Printer>,
226) -> impl Future<Item = Vec<SomaContainer>, Error = Error> {
227 let soma_filter = SomaFilterBuilder::new().append_user(env.username()).build();
228 env.docker
229 .list_containers(Some(ListContainersOptions::<String> {
230 all: true,
231 filters: soma_filter,
232 ..Default::default()
233 }))
234 .map(move |containers| -> Vec<SomaContainer> {
235 containers
236 .into_iter()
237 .filter_map(|container| {
238 let labels = &container.labels;
239 if let (Some(repo_name), Some(prob_name)) = (
240 labels.get(LABEL_KEY_REPOSITORY),
241 labels.get(LABEL_KEY_PROBLEM),
242 ) {
243 let status = match labels.get(LABEL_KEY_VERSION) {
244 Some(version) if version == VERSION => VersionStatus::Normal,
245 Some(_) => VersionStatus::VersionMismatch,
246 None => VersionStatus::NoVersionFound,
247 };
248 Some(SomaContainer::new(
249 repo_name.to_owned(),
250 prob_name.to_owned(),
251 container,
252 status,
253 ))
254 } else {
255 None
256 }
257 })
258 .collect()
259 })
260}
261
262pub fn list_images(
263 env: &Environment<impl Connect, impl Printer>,
264) -> impl Future<Item = Vec<SomaImage>, Error = Error> {
265 let soma_filter = SomaFilterBuilder::new().append_user(env.username()).build();
266 env.docker
267 .list_images(Some(ListImagesOptions::<String> {
268 filters: soma_filter,
269 ..Default::default()
270 }))
271 .map(move |images| -> Vec<SomaImage> {
272 images
273 .into_iter()
274 .filter_map(|image| {
275 let labels = image.labels.as_ref().unwrap();
277 if let (Some(repo_name), Some(prob_name)) = (
278 labels.get(LABEL_KEY_REPOSITORY),
279 labels.get(LABEL_KEY_PROBLEM),
280 ) {
281 let status = match labels.get(LABEL_KEY_VERSION) {
282 Some(version) if version == VERSION => VersionStatus::Normal,
283 Some(_) => VersionStatus::VersionMismatch,
284 None => VersionStatus::NoVersionFound,
285 };
286 Some(SomaImage::new(
287 repo_name.to_owned(),
288 prob_name.to_owned(),
289 image,
290 status,
291 ))
292 } else {
293 None
294 }
295 })
296 .collect()
297 })
298}
299
300pub fn build<'a>(
301 env: &'a Environment<impl Connect, impl Printer>,
302 labels: DockerLabel<'a>,
303 image_name: &'a str,
304 build_context: Vec<u8>,
305) -> impl Future<Item = (), Error = Error> + 'a {
306 let build_options = BuildImageOptions {
307 t: image_name,
308 pull: true,
309 forcerm: true,
310 labels,
311 ..Default::default()
312 };
313
314 env.docker
315 .build_image(build_options, None, Some(build_context.into()))
316 .fold((), move |_, build_image_result| {
317 use bollard::image::BuildImageResults::*;
318 match build_image_result {
319 BuildImageStream { stream } => {
320 let message = stream.trim();
321 if message != "" {
322 env.printer().write_line(message)
323 }
324 Ok(())
325 }
326 BuildImageError { error, .. } => {
327 env.printer().write_line(error.trim());
328 Err(SomaError::DockerBuildFailed)
329 }
330 _ => Ok(()),
331 }
332 })
333}
334
335pub fn docker_labels<'a>(
336 env: &'a Environment<impl Connect, impl Printer>,
337 problem: &'a Problem,
338) -> DockerLabel<'a> {
339 vec![
340 (LABEL_KEY_VERSION, VERSION),
341 (LABEL_KEY_USERNAME, env.username()),
342 (LABEL_KEY_REPOSITORY, problem.repo_name()),
343 (LABEL_KEY_PROBLEM, problem.prob_name()),
344 ]
345 .into_iter()
346 .collect()
347}
348
349pub fn create<'a>(
350 env: &'a Environment<impl Connect, impl Printer>,
351 labels: DockerLabel<'a>,
352 image_name: &'a str,
353 port_str: &'a str,
354) -> impl Future<Item = String, Error = Error> + 'a {
355 let mut port_bindings = HashMap::new();
356 port_bindings.insert(
357 "1337/tcp",
358 vec![PortBinding {
359 host_ip: "",
360 host_port: port_str,
361 }],
362 );
363
364 let host_config = HostConfig {
365 port_bindings: Some(port_bindings),
366 ..Default::default()
367 };
368
369 env.docker
370 .create_container(
371 None::<CreateContainerOptions<String>>,
372 Config {
373 image: Some(image_name),
374 labels: Some(labels),
375 host_config: Some(host_config),
376 ..Default::default()
377 },
378 )
379 .map(|container_results| container_results.id)
380}
381
382pub fn remove_image(
383 env: &Environment<impl Connect, impl Printer>,
384 image_name: &str,
385) -> impl Future<Item = (), Error = Error> {
386 env.docker
387 .remove_image(image_name, None::<RemoveImageOptions>, None)
388 .map(|_| ())
389}
390
391pub fn remove_container(
392 env: &Environment<impl Connect, impl Printer>,
393 container_id: &str,
394) -> impl Future<Item = (), Error = Error> {
395 env.docker
396 .remove_container(container_id, None::<RemoveContainerOptions>)
397}
398
399pub fn prune_images_from_prob(
400 env: &Environment<impl Connect, impl Printer>,
401 problem: &Problem,
402) -> impl Future<Item = (), Error = Error> {
403 let soma_filter = SomaFilterBuilder::new()
404 .append_user(env.username())
405 .append_prob(problem)
406 .build();
407 env.docker
408 .prune_images(Some(PruneImagesOptions {
409 filters: soma_filter,
410 }))
411 .map(|_| ())
412}
413
414pub fn prune_containers_from_prob(
415 env: &Environment<impl Connect, impl Printer>,
416 problem: &Problem,
417) -> impl Future<Item = (), Error = Error> {
418 let soma_filter = SomaFilterBuilder::new()
419 .append_user(env.username())
420 .append_prob(problem)
421 .build();
422 env.docker
423 .prune_containers(Some(PruneContainersOptions {
424 filters: soma_filter,
425 }))
426 .map(|_| ())
427}
428
429pub fn start(
430 env: &Environment<impl Connect, impl Printer>,
431 container_id: &str,
432) -> impl Future<Item = (), Error = Error> {
433 env.docker
434 .start_container(container_id, None::<StartContainerOptions<String>>)
435}
436
437pub fn stop(
438 env: &Environment<impl Connect, impl Printer>,
439 container_id: &str,
440) -> impl Future<Item = (), Error = Error> {
441 env.docker
442 .stop_container(container_id, None::<StopContainerOptions>)
443}