12
12
pub mod providers;
13
13
14
14
use errors:: * ;
15
- use futures:: Future ;
15
+ use futures:: { future , Future , Poll } ;
16
16
use futures:: stream:: Stream ;
17
17
use futures:: sync:: oneshot;
18
18
use host:: Host ;
@@ -27,11 +27,6 @@ const DEFAULT_SHELL: [&'static str; 2] = ["/bin/sh", "-c"];
27
27
#[ cfg( windows) ]
28
28
const DEFAULT_SHELL : [ & ' static str ; 1 ] = [ "yeah...we don't currently support windows :(" ] ;
29
29
30
- pub type ExecResult = Box < Future < Item = (
31
- Box < Stream < Item = String , Error = Error > > ,
32
- Box < Future < Item = ExitStatus , Error = Error > >
33
- ) , Error = Error > > ;
34
-
35
30
/// Represents a shell command to be executed on a host.
36
31
///
37
32
///## Examples
@@ -54,13 +49,20 @@ pub type ExecResult = Box<Future<Item = (
54
49
///let host = Local::new(&handle).wait().unwrap();
55
50
///
56
51
///let cmd = Command::new(&host, "ls /path/to/foo", None);
57
- ///let result = cmd.exec().and_then(|(stream, status) | {
52
+ ///let result = cmd.exec().and_then(|mut status| {
58
53
/// // Print the command's stdout/stderr to stdout
59
- /// stream.for_each(|line| { println!("{}", line); Ok(()) })
60
- /// // When it's ready, also print the exit status
54
+ /// status.take_stream().unwrap()
55
+ /// .for_each(|line| { println!("{}", line); Ok(()) })
56
+ /// // On its own, the stream will not do anything, so we need to make
57
+ /// // sure it gets returned along with the status future. `join()` will
58
+ /// // mash the two together so we can return them as one.
61
59
/// .join(status.map(|s| println!("This command {} {}",
62
60
/// if s.success { "succeeded" } else { "failed" },
63
- /// if let Some(e) = s.code { format!("with code {}", e) } else { String::new() })))
61
+ /// if let Some(e) = s.code {
62
+ /// format!("with code {}", e)
63
+ /// } else {
64
+ /// String::new()
65
+ /// })))
64
66
///});
65
67
///
66
68
///core.run(result).unwrap();
@@ -71,13 +73,13 @@ pub type ExecResult = Box<Future<Item = (
71
73
/// this as you could run out of memory on your heap if the output buffer is
72
74
/// too big.
73
75
///
74
- ///```
76
+ ///```no_run
75
77
///extern crate futures;
76
78
///extern crate intecture_api;
77
79
///extern crate tokio_core;
78
80
///
79
- ///use futures::{future, Future, Stream} ;
80
- ///use intecture_api::errors::Error ;
81
+ ///use futures::Future;
82
+ ///use intecture_api::errors::* ;
81
83
///use intecture_api::prelude::*;
82
84
///use tokio_core::reactor::Core;
83
85
///
@@ -88,14 +90,20 @@ pub type ExecResult = Box<Future<Item = (
88
90
///let host = Local::new(&handle).wait().unwrap();
89
91
///
90
92
///let cmd = Command::new(&host, "ls /path/to/foo", None);
91
- ///let result = cmd.exec().and_then(|(stream, _)| {
92
- /// // Concatenate the buffer into a `String`
93
- /// stream.fold(String::new(), |mut acc, line| {
94
- /// acc.push_str(&line);
95
- /// future::ok::<_, Error>(acc)
96
- /// })
93
+ ///let result = cmd.exec().and_then(|status| {
94
+ /// status.result().unwrap()
97
95
/// .map(|_output| {
98
- /// // The binding `output` is our accumulated buffer
96
+ /// // Our command finished successfully. Now we can do something
97
+ /// // with our output here.
98
+ /// })
99
+ /// .map_err(|e| {
100
+ /// // Our command errored out. Let's grab the output and see what
101
+ /// // went wrong.
102
+ /// match *e.kind() {
103
+ /// ErrorKind::Command(ref output) => println!("Oh noes! {}", output),
104
+ /// _ => unreachable!(),
105
+ /// }
106
+ /// e
99
107
/// })
100
108
///});
101
109
///
@@ -122,12 +130,10 @@ pub type ExecResult = Box<Future<Item = (
122
130
///let host = Local::new(&handle).wait().unwrap();
123
131
///
124
132
///let cmd = Command::new(&host, "ls /path/to/foo", None);
125
- ///let result = cmd.exec().and_then(|(stream, status)| {
126
- /// // Discard the buffer
127
- /// stream.for_each(|_| Ok(()))
128
- /// .join(status.map(|_status| {
129
- /// // Enjoy the status, baby...
130
- /// }))
133
+ ///let result = cmd.exec().and_then(|mut status| {
134
+ /// status.map(|_exit_status| {
135
+ /// // Enjoy the status, baby!
136
+ /// })
131
137
///});
132
138
///
133
139
///core.run(result).unwrap();
@@ -139,6 +145,20 @@ pub struct Command<H: Host> {
139
145
cmd : Vec < String > ,
140
146
}
141
147
148
+ /// Represents the status of a running `Command`, including the output stream
149
+ /// and exit status.
150
+ pub struct CommandStatus {
151
+ stream : Option < Box < Stream < Item = String , Error = Error > > > ,
152
+ exit_status : Option < Box < Future < Item = ExitStatus , Error = Error > > > ,
153
+ }
154
+
155
+ /// Represents the exit status of a `Command` as a `Result`-like `Future`. If
156
+ /// the command succeeded, the command output is returned. If it failed, an
157
+ /// error containing the command's output is returned.
158
+ pub struct CommandResult {
159
+ inner : Box < Future < Item = String , Error = Error > > ,
160
+ }
161
+
142
162
/// The status of a finished command.
143
163
///
144
164
/// This is a serializable replica of
@@ -231,42 +251,33 @@ impl<H: Host + 'static> Command<H> {
231
251
///
232
252
/// This is the error you'll see if you prematurely drop the output `Stream`
233
253
/// while trying to resolve the `Future<Item = ExitStatus, ...>`.
234
- pub fn exec ( & self ) -> ExecResult {
254
+ pub fn exec ( & self ) -> Box < Future < Item = CommandStatus , Error = Error > > {
235
255
let request = Request :: CommandExec ( self . provider . as_ref ( ) . map ( |p| p. name ( ) ) , self . cmd . clone ( ) ) ;
236
256
Box :: new ( self . host . request ( request)
237
257
. chain_err ( || ErrorKind :: Request { endpoint : "Command" , func : "exec" } )
238
258
. map ( |msg| {
239
- parse_body_stream ( msg)
259
+ CommandStatus :: new ( msg)
240
260
} ) )
241
261
}
242
262
}
243
263
244
- // Abstract this logic so other endpoints can parse CommandProvider::exec()
245
- // streams too.
246
- #[ doc( hidden) ]
247
- pub fn parse_body_stream ( mut msg : Message < Response , Body < Vec < u8 > , io:: Error > > ) ->
248
- (
249
- Box < Stream < Item = String , Error = Error > > ,
250
- Box < Future < Item = ExitStatus , Error = Error > >
251
- )
252
- {
253
- let ( tx, rx) = oneshot:: channel :: < ExitStatus > ( ) ;
254
- let mut tx_share = Some ( tx) ;
255
- let mut found = false ;
256
- (
257
- Box :: new ( msg. take_body ( )
264
+ impl CommandStatus {
265
+ #[ doc( hidden) ]
266
+ pub fn new ( mut msg : Message < Response , Body < Vec < u8 > , io:: Error > > ) -> CommandStatus {
267
+ let ( tx, rx) = oneshot:: channel :: < ExitStatus > ( ) ;
268
+ let mut tx = Some ( tx) ;
269
+ let stream = msg. take_body ( )
258
270
. expect ( "Command::exec reply missing body stream" )
259
271
. filter_map ( move |v| {
260
272
let s = String :: from_utf8_lossy ( & v) . to_string ( ) ;
261
273
262
274
// @todo This is a heuristical approach which is fallible
263
- if !found && s. starts_with ( "ExitStatus:" ) {
275
+ if s. starts_with ( "ExitStatus:" ) {
264
276
let ( _, json) = s. split_at ( 11 ) ;
265
277
match serde_json:: from_str ( json) {
266
278
Ok ( status) => {
267
279
// @todo What should happen if this fails?
268
- let _ = tx_share. take ( ) . unwrap ( ) . send ( status) ;
269
- found = true ;
280
+ let _ = tx. take ( ) . unwrap ( ) . send ( status) ;
270
281
return None ;
271
282
} ,
272
283
_ => ( ) ,
@@ -275,9 +286,73 @@ pub fn parse_body_stream(mut msg: Message<Response, Body<Vec<u8>, io::Error>>) -
275
286
276
287
Some ( s)
277
288
} )
278
- . then ( |r| r. chain_err ( || "Command execution failed" ) )
279
- ) as Box < Stream < Item = String , Error = Error > > ,
280
- Box :: new ( rx. chain_err ( || "Buffer dropped before ExitStatus was sent" ) )
281
- as Box < Future < Item = ExitStatus , Error = Error > >
282
- )
289
+ . then ( |r| r. chain_err ( || "Command execution failed" ) ) ;
290
+
291
+ let exit_status = rx. chain_err ( || "Buffer dropped before ExitStatus was sent" ) ;
292
+
293
+ CommandStatus {
294
+ stream : Some ( Box :: new ( stream) ) ,
295
+ exit_status : Some ( Box :: new ( exit_status) ) ,
296
+ }
297
+ }
298
+
299
+ /// Take ownership of the output stream.
300
+ ///
301
+ /// The stream is guaranteed to be present only if this is the first call
302
+ /// to `take_stream()` and the future has not yet been polled.
303
+ pub fn take_stream ( & mut self ) -> Option < Box < Stream < Item = String , Error = Error > > > {
304
+ self . stream . take ( )
305
+ }
306
+
307
+ /// Convert this to a `CommandResult`, which returns the output string on
308
+ /// success and an error containing the command's output on failure. If the
309
+ /// stream has already been taken by `take_stream()` then this function
310
+ /// will return `None`.
311
+ ///
312
+ /// Note that "success" is determined by examining the `ExitStatus::success`
313
+ /// bool. See `ExitStatus` docs for details.
314
+ pub fn result ( self ) -> Option < CommandResult > {
315
+ if let Some ( stream) = self . stream {
316
+ let inner = stream. fold ( String :: new ( ) , |mut acc, line| {
317
+ acc. push_str ( & line) ;
318
+ future:: ok :: < _ , Error > ( acc)
319
+ } )
320
+ . join ( self . exit_status . unwrap ( ) )
321
+ . and_then ( |( output, status) | if status. success {
322
+ future:: ok ( output)
323
+ } else {
324
+ future:: err ( ErrorKind :: Command ( output) . into ( ) )
325
+ } ) ;
326
+
327
+ Some ( CommandResult {
328
+ inner : Box :: new ( inner) as Box < Future < Item = String , Error = Error > >
329
+ } )
330
+ } else {
331
+ None
332
+ }
333
+ }
334
+ }
335
+
336
+ impl Future for CommandStatus {
337
+ type Item = ExitStatus ;
338
+ type Error = Error ;
339
+
340
+ fn poll ( & mut self ) -> Poll < Self :: Item , Self :: Error > {
341
+ if let Some ( stream) = self . stream . take ( ) {
342
+ self . exit_status = Some ( Box :: new ( stream. for_each ( |_| Ok ( ( ) ) )
343
+ . join ( self . exit_status . take ( ) . unwrap ( ) )
344
+ . map ( |( _, status) | status) ) ) ;
345
+ }
346
+
347
+ self . exit_status . as_mut ( ) . unwrap ( ) . poll ( )
348
+ }
349
+ }
350
+
351
+ impl Future for CommandResult {
352
+ type Item = String ;
353
+ type Error = Error ;
354
+
355
+ fn poll ( & mut self ) -> Poll < Self :: Item , Self :: Error > {
356
+ self . inner . poll ( )
357
+ }
283
358
}
0 commit comments