1extern 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
21pub 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#[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#[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#[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
89fn 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); 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 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
317pub struct BisectOptions {
319 pub timeout_in_seconds: usize,
320 pub trunc_size: usize,
321}
322
323pub 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}