docker_bisect/
lib.rs

1//! # docker-bisect
2//! `docker-bisect` create assumes that the docker daemon is running and that you have a
3//! docker image with cached layers to probe.
4extern crate colored;
5extern crate dockworker;
6extern crate indicatif;
7extern crate rand;
8
9use std::clone::Clone;
10use std::fmt;
11use std::io::{prelude::*, Error, ErrorKind};
12use std::sync::Arc;
13use std::thread;
14use std::time::{Duration, SystemTime};
15
16use colored::*;
17use dockworker::*;
18use indicatif::ProgressBar;
19use rand::Rng;
20
21/// Truncates a string to a single line with a max width
22/// and removes docker prefixes.
23///
24/// # Example
25/// ```
26/// use docker_bisect::truncate;
27/// let line = "blar #(nop) real command\n line 2";
28/// assert_eq!("real com", truncate(&line, 8));
29/// ```
30pub fn truncate(mut s: &str, max_chars: usize) -> &str {
31    s = s.lines().next().expect("nothing to truncate");
32    if s.contains("#(nop) ") {
33        let mut splat = s.split(" #(nop) ");
34        let _ = splat.next();
35        s = splat.next().expect("#(nop) with no command in.");
36        s = s.trim();
37    }
38    match s.char_indices().nth(max_chars) {
39        None => s,
40        Some((idx, _)) => &s[..idx],
41    }
42}
43
44/// A layer in a docker image. (A layer is a set of files changed due to the previous command).
45#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq)]
46pub struct Layer {
47    pub height: usize,
48    pub image_name: String,
49    pub creation_command: String,
50}
51
52impl fmt::Display for Layer {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        write!(f, "{} | {:?}", self.image_name, self.creation_command)
55    }
56}
57
58/// The stderr/stdout of running the command on a container made of this layer
59/// (on top of all earlier layers). If command hit the timeout the result may be truncated or empty.
60#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq)]
61pub struct LayerResult {
62    pub layer: Layer,
63    pub result: String,
64}
65
66impl fmt::Display for LayerResult {
67    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
68        write!(f, "{} | {}", self.layer, self.result)
69    }
70}
71
72/// A Transition is the LayerResult of running the command on the lower layer
73/// and of running the command on the higher layer. No-op transitions are not recorded.
74#[derive(Debug, Eq, Ord, PartialOrd, PartialEq)]
75pub struct Transition {
76    pub before: Option<LayerResult>,
77    pub after: LayerResult,
78}
79
80impl fmt::Display for Transition {
81    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82        match &self.before {
83            Some(be) => write!(f, "({} -> {})", be, self.after),
84            None => write!(f, "-> {}", self.after),
85        }
86    }
87}
88
89/// Starts the bisect operation. Calculates highest and lowest layer result and if they have
90/// different outputs it starts a binary chop to figure out which layer(s) caused the change.
91fn get_changes<T>(layers: Vec<Layer>, action: &T) -> Result<Vec<Transition>, Error>
92where
93    T: ContainerAction + 'static,
94{
95    let first_layer = layers.first().expect("no first layer");
96    let last_layer = layers.last().expect("no last layer");
97
98    let first_image_name: String = first_layer.image_name.clone();
99    let last_image_name = &last_layer.image_name;
100
101    let action_c = action.clone();
102    let left_handle = thread::spawn(move || action_c.try_container(&first_image_name));
103
104    let end = action.try_container(last_image_name);
105    let start = left_handle.join().expect("first layer execution error!");
106
107    if start == end {
108        return Ok(vec![Transition {
109            before: None,
110            after: LayerResult {
111                layer: last_layer.clone(),
112                result: start,
113            },
114        }]);
115    }
116
117    bisect(
118        Vec::from(&layers[1..layers.len() - 1]),
119        LayerResult {
120            layer: first_layer.clone(),
121            result: start,
122        },
123        LayerResult {
124            layer: last_layer.clone(),
125            result: end,
126        },
127        action,
128    )
129}
130
131fn bisect<T>(
132    history: Vec<Layer>,
133    start: LayerResult,
134    end: LayerResult,
135    action: &T,
136) -> Result<Vec<Transition>, Error>
137where
138    T: ContainerAction + 'static,
139{
140    let size = history.len();
141    if size == 0 {
142        if start.result == end.result {
143            return Err(Error::new(std::io::ErrorKind::Other, ""));
144        }
145        return Ok(vec![Transition {
146            before: Some(start.clone()),
147            after: end.clone(),
148        }]);
149    }
150
151    let half = size / 2;
152    let mid_result = LayerResult {
153        layer: history[half].clone(),
154        result: action.try_container(&history[half].image_name),
155    };
156
157    if size == 1 {
158        let mut results = Vec::<Transition>::new();
159        if *start.result != mid_result.result {
160            results.push(Transition {
161                before: Some(start.clone()),
162                after: mid_result.clone(),
163            });
164        }
165        if mid_result.result != *end.result {
166            results.push(Transition {
167                before: Some(mid_result),
168                after: end.clone(),
169            });
170        }
171        return Ok(results);
172    }
173
174    if start.result == mid_result.result {
175        action.skip((mid_result.layer.height - start.layer.height) as u64);
176        return bisect(Vec::from(&history[half + 1..]), mid_result, end, action);
177    }
178    if mid_result.result == end.result {
179        action.skip((end.layer.height - mid_result.layer.height) as u64);
180        return bisect(Vec::from(&history[..half]), start, mid_result, action);
181    }
182
183    let clone_a = action.clone();
184    let clone_b = action.clone();
185    let mid_result_c = mid_result.clone();
186
187    let hist_a = Vec::from(&history[..half]);
188
189    let left_handle = thread::spawn(move || bisect(hist_a, start, mid_result, &clone_a));
190    let right_handle =
191        thread::spawn(move || bisect(Vec::from(&history[half + 1..]), mid_result_c, end, &clone_b));
192    let mut left_results: Vec<Transition> = left_handle
193        .join()
194        .expect("left")
195        .expect("left transition err");
196
197    let right_results: Vec<Transition> = right_handle
198        .join()
199        .expect("right")
200        .expect("right transition err");
201
202    left_results.extend(right_results); // These results are sorted later...
203    Ok(left_results)
204}
205
206trait ContainerAction: Clone + Send {
207    fn try_container(&self, container_id: &str) -> String;
208    fn skip(&self, count: u64) -> ();
209}
210
211#[derive(Clone)]
212struct DockerContainer {
213    pb: Arc<ProgressBar>,
214    command_line: Vec<String>,
215    timeout_in_seconds: usize,
216}
217
218impl DockerContainer {
219    fn new(total: u64, command_line: Vec<String>, timeout_in_seconds: usize) -> DockerContainer {
220        let pb = Arc::new(ProgressBar::new(total));
221
222        DockerContainer {
223            pb,
224            command_line,
225            timeout_in_seconds,
226        }
227    }
228}
229
230struct Guard<'a> { buf: &'a mut Vec<u8>, len: usize }
231
232impl<'a> Drop for Guard<'a> {
233    fn drop(&mut self) {
234        unsafe { self.buf.set_len(self.len); }
235    }
236}
237
238impl ContainerAction for DockerContainer {
239    fn try_container(&self, container_id: &str) -> String {
240        let docker: Docker = Docker::connect_with_defaults().expect("docker daemon running?");
241        let container_name: String = rand::thread_rng().gen_range(0., 1.3e4).to_string();
242
243        //Create container
244        let mut create = ContainerCreateOptions::new(&container_id);
245        let mut host_config = ContainerHostConfig::new();
246        host_config.auto_remove(false);
247        create.host_config(host_config);
248        let it = self.command_line.iter();
249        for command in it {
250            create.cmd(command.clone());
251        }
252
253        let container: CreateContainerResponse = docker
254            .create_container(Some(&container_name), &create)
255            .expect("couldn't create container");
256
257        let result = docker.start_container(&container.id);
258        if result.is_err() {
259            let err: dockworker::errors::Error = result.unwrap_err();
260
261            return format!("{}", err);
262        }
263
264        let log_options = ContainerLogOptions {
265            stdout: true,
266            stderr: true,
267            since: None,
268            timestamps: None,
269            tail: None,
270            follow: true,
271        };
272
273        let timeout = Duration::from_secs(self.timeout_in_seconds as u64);
274
275        let mut container_output = String::new();
276
277        let now = SystemTime::now();
278        let timeout_time = now + timeout;
279
280        let result = docker.log_container(&container_name, &log_options);
281        if let Ok(result) = result {
282            let mut r = result;
283
284            let reservation_size = 32;
285            let mut buf = Vec::<u8>::new();
286            {
287                let mut g = Guard { len: buf.len(), buf: &mut buf };
288                loop {
289                    if g.len == g.buf.len() {
290                        g.buf.resize(g.len + reservation_size, 0);
291                    }
292                    match r.read(&mut g.buf[g.len..]) {
293                        Ok(0) => { break; }
294                        Ok(n) => g.len += n,
295                        Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
296                        Err(_e) => { break; }
297                    }
298                    if SystemTime::now() > timeout_time {
299                        break;
300                    }
301                }
302            }
303
304            container_output = String::from_utf8_lossy(&buf).to_string();
305        }
306
307        self.pb.inc(1);
308        let _stop_result = docker.stop_container(&container.id, timeout);
309        container_output
310    }
311
312    fn skip(&self, count: u64) -> () {
313        self.pb.inc(count);
314    }
315}
316
317/// Struct to hold parameters.
318pub struct BisectOptions {
319    pub timeout_in_seconds: usize,
320    pub trunc_size: usize,
321}
322
323/// Create containers based on layers and run command_line against them.
324/// Result is the differences in std out and std err.
325pub fn try_bisect(
326    histories: &Vec<ImageLayer>,
327    command_line: Vec<String>,
328    options: BisectOptions,
329) -> Result<Vec<Transition>, Error> {
330    println!(
331        "\n{}\n\n{:?}\n",
332        "Command to apply to layers:".bold(),
333        &command_line
334    );
335    let create_and_try_container = DockerContainer::new(
336        histories.len() as u64,
337        command_line,
338        options.timeout_in_seconds,
339    );
340
341    println!("{}", "Skipped missing layers:".bold());
342    println!();
343
344    let mut layers = Vec::new();
345    for (index, event) in histories.iter().rev().enumerate() {
346        let mut created = event.created_by.clone();
347        created = truncate(&created, options.trunc_size).to_string();
348        match event.id.clone() {
349            Some(layer_name) => layers.push(Layer {
350                height: index,
351                image_name: layer_name,
352                creation_command: event.created_by.clone(),
353            }),
354            None => println!("{:<3}: {}.", index, truncate(&created, options.trunc_size)),
355        }
356    }
357
358    println!();
359    println!(
360        "{}",
361        "Bisecting found layers (running command on the layers) ==>\n".bold()
362    );
363
364    if layers.len() < 2 {
365        println!();
366        eprintln!(
367            "{} layers found in cache - not enough layers to bisect.",
368            layers.len()
369        );
370        return Err(Error::new(
371            std::io::ErrorKind::Other,
372            "no cached layers found!",
373        ));
374    }
375
376    let results = get_changes(layers, &create_and_try_container);
377    create_and_try_container.pb.finish_with_message("done");
378    results
379}
380
381#[cfg(test)]
382mod tests {
383    use super::*;
384    use std::collections::HashMap;
385
386    #[derive(Clone)]
387    struct MapAction {
388        map: HashMap<String, String>,
389    }
390
391    impl MapAction {
392        fn new(from: Vec<usize>, to: Vec<&str>) -> Self {
393            let mut object = MapAction {
394                map: HashMap::new(),
395            };
396            for (f, t) in from.iter().zip(to.iter()) {
397                object.map.insert(f.to_string(), t.to_string());
398            }
399            object
400        }
401    }
402
403    impl ContainerAction for MapAction {
404        fn try_container(&self, container_id: &str) -> String {
405            let none = String::new();
406            let result: &String = self.map.get(container_id).unwrap_or(&none);
407            result.clone()
408        }
409
410        fn skip(&self, _count: u64) -> () {}
411    }
412
413    fn lay(id: usize) -> Layer {
414        Layer {
415            height: id,
416            image_name: id.to_string(),
417            creation_command: id.to_string(),
418        }
419    }
420
421    #[test]
422    fn if_output_always_same_return_earliest_command() {
423        let results = get_changes(
424            vec![lay(1), lay(2), lay(3)],
425            &MapAction::new(vec![1, 2, 3], vec!["A", "A", "A"]),
426        );
427
428        assert_eq!(
429            results.unwrap(),
430            vec![Transition {
431                before: None,
432                after: LayerResult {
433                    layer: lay(3),
434                    result: "A".to_string()
435                },
436            }]
437        );
438    }
439
440    #[test]
441    fn if_one_difference_show_command_that_made_difference() {
442        let results = get_changes(
443            vec![lay(1), lay(2), lay(3)],
444            &MapAction::new(vec![1, 2, 3], vec!["A", "A", "B"]),
445        );
446
447        assert_eq!(
448            results.unwrap(),
449            vec![Transition {
450                before: Some(LayerResult {
451                    layer: lay(2),
452                    result: "A".to_string()
453                }),
454                after: LayerResult {
455                    layer: lay(3),
456                    result: "B".to_string()
457                },
458            }]
459        );
460    }
461
462    #[test]
463    fn if_two_differences_show_two_commands_that_made_difference() {
464        let results = get_changes(
465            vec![lay(1), lay(2), lay(3), lay(4)],
466            &MapAction::new(vec![1, 2, 3, 4], vec!["A", "B", "B", "C"]),
467        );
468
469        let res = results.unwrap();
470
471        assert_eq!(
472            res,
473            vec![
474                Transition {
475                    before: Some(LayerResult {
476                        layer: lay(1),
477                        result: "A".to_string()
478                    }),
479                    after: LayerResult {
480                        layer: lay(2),
481                        result: "B".to_string()
482                    },
483                },
484                Transition {
485                    before: Some(LayerResult {
486                        layer: lay(3),
487                        result: "B".to_string()
488                    }),
489                    after: LayerResult {
490                        layer: lay(4),
491                        result: "C".to_string()
492                    },
493                }
494            ]
495        );
496    }
497
498    #[test]
499    fn three_transitions() {
500        let results = get_changes(
501            vec![
502                lay(1),
503                lay(2),
504                lay(3),
505                lay(4),
506                lay(5),
507                lay(6),
508                lay(7),
509                lay(8),
510                lay(9),
511                lay(10),
512            ],
513            &MapAction::new(
514                vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
515                vec!["A", "B", "B", "C", "C", "C", "C", "C", "D", "D"],
516            ),
517        );
518        let res = results.unwrap();
519
520        assert_eq!(
521            res,
522            vec![
523                Transition {
524                    before: Some(LayerResult {
525                        layer: lay(1),
526                        result: "A".to_string()
527                    }),
528                    after: LayerResult {
529                        layer: lay(2),
530                        result: "B".to_string()
531                    },
532                },
533                Transition {
534                    before: Some(LayerResult {
535                        layer: lay(3),
536                        result: "B".to_string()
537                    }),
538                    after: LayerResult {
539                        layer: lay(4),
540                        result: "C".to_string()
541                    },
542                },
543                Transition {
544                    before: Some(LayerResult {
545                        layer: lay(8),
546                        result: "C".to_string()
547                    }),
548                    after: LayerResult {
549                        layer: lay(9),
550                        result: "D".to_string()
551                    },
552                }
553            ]
554        );
555    }
556}