Skip to content

Commit 65fd377

Browse files
committed
Restructure providers to abstract provider logic from endpoints. Hamstrung by lack of specialisation in Rust :#
1 parent 294677e commit 65fd377

File tree

20 files changed

+912
-559
lines changed

20 files changed

+912
-559
lines changed

agent/src/main.rs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,18 @@ mod errors;
1919

2020
use errors::*;
2121
use futures::{future, Future};
22-
use intecture_api::{Executable, Runnable};
22+
use intecture_api::remote::{Executable, Runnable};
23+
use intecture_api::host::local::Local;
2324
use intecture_api::host::remote::JsonProto;
2425
use std::fs::File;
2526
use std::io::{self, Read};
2627
use std::net::SocketAddr;
2728
use tokio_proto::TcpServer;
2829
use tokio_service::Service;
2930

30-
pub struct Api;
31+
pub struct Api {
32+
host: Local,
33+
}
3134

3235
impl Service for Api {
3336
type Request = serde_json::Value;
@@ -46,7 +49,7 @@ impl Service for Api {
4649
io::ErrorKind::Other, e.description()
4750
))),
4851
};
49-
Box::new(runnable.exec()
52+
Box::new(runnable.exec(&self.host)
5053
// @todo Can't wrap 'e' as error_chain Error doesn't derive Sync.
5154
// Waiting for https://github.com/rust-lang-nursery/error-chain/pull/163
5255
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.description()))
@@ -96,7 +99,8 @@ quick_main!(|| -> Result<()> {
9699
Config { address }
97100
};
98101

102+
let host = Local::new().wait()?;
99103
let server = TcpServer::new(JsonProto, config.address);
100-
server.serve(|| Ok(Api));
104+
server.serve(move || Ok(Api { host: host.clone() }));
101105
Ok(())
102106
});

core/src/command/mod.rs

Lines changed: 38 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,18 @@
88
99
pub mod providers;
1010

11-
use erased_serde::Serialize;
1211
use errors::*;
13-
use Executable;
1412
use futures::{future, Future};
1513
use host::Host;
16-
use self::providers::{Nix, NixRunnable};
17-
use std::sync::Arc;
14+
use self::providers::CommandProvider;
1815

19-
pub trait CommandProvider<H: Host> {
20-
fn available(&Arc<H>) -> Box<Future<Item = bool, Error = Error>> where Self: Sized;
21-
fn try_new(&Arc<H>, &str, Option<&[&str]>) -> Box<Future<Item = Option<Self>, Error = Error>> where Self: Sized;
22-
fn exec(&mut self) -> Box<Future<Item = CommandResult, Error = Error>>;
23-
}
24-
25-
#[doc(hidden)]
26-
#[derive(Serialize, Deserialize)]
27-
pub enum CommandRunnable {
28-
Nix(NixRunnable)
29-
}
16+
#[cfg(not(windows))]
17+
const DEFAULT_SHELL: [&'static str; 2] = ["/bin/sh", "-c"];
18+
#[cfg(windows)]
19+
const DEFAULT_SHELL: [&'static str; 1] = ["yeah...we don't currently support windows :("];
3020

31-
#[doc(hidden)]
32-
#[derive(Clone, Serialize, Deserialize)]
33-
pub struct Command {
21+
pub struct Command<H: Host> {
22+
inner: Box<CommandProvider<H>>,
3423
shell: Vec<String>,
3524
cmd: String,
3625
}
@@ -43,18 +32,38 @@ pub struct CommandResult {
4332
pub stderr: Vec<u8>,
4433
}
4534

46-
impl Executable for CommandRunnable {
47-
fn exec(self) -> Box<Future<Item = Box<Serialize>, Error = Error>> {
48-
match self {
49-
CommandRunnable::Nix(p) => p.exec()
35+
impl<H: Host + 'static> Command<H> {
36+
pub fn new(host: &H, cmd: &str, shell: Option<&[&str]>) -> Box<Future<Item = Command<H>, Error = Error>> {
37+
let cmd_owned = cmd.to_owned();
38+
let shell_owned: Vec<String> = shell.unwrap_or(&DEFAULT_SHELL)
39+
.to_owned()
40+
.iter()
41+
.map(|s| s.to_string())
42+
.collect();
43+
44+
Box::new(host.command_provider()
45+
.and_then(|provider| future::ok(Command {
46+
inner: provider,
47+
shell: shell_owned,
48+
cmd: cmd_owned,
49+
})))
50+
}
51+
52+
pub fn with_provider<P>(provider: P, cmd: &str, shell: Option<&[&str]>) -> Command<H>
53+
where P: CommandProvider<H> + 'static
54+
{
55+
Command {
56+
inner: Box::new(provider),
57+
shell: shell.unwrap_or(&DEFAULT_SHELL)
58+
.to_owned()
59+
.iter()
60+
.map(|s| s.to_string())
61+
.collect(),
62+
cmd: cmd.into(),
5063
}
5164
}
52-
}
5365

54-
pub fn factory<H: Host + 'static>(host: &Arc<H>, cmd: &str, shell: Option<&[&str]>) -> Box<Future<Item = Box<CommandProvider<H>>, Error = Error>> {
55-
Box::new(Nix::try_new(host, cmd, shell)
56-
.and_then(|opt| match opt {
57-
Some(provider) => future::ok(Box::new(provider) as Box<CommandProvider<H>>),
58-
None => future::err(ErrorKind::ProviderUnavailable("Command").into())
59-
}))
66+
pub fn exec(&mut self) -> Box<Future<Item = CommandResult, Error = Error>> {
67+
self.inner.exec(&self.cmd, &self.shell)
68+
}
6069
}

core/src/command/providers/generic.rs

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
// Copyright 2015-2017 Intecture Developers.
2+
//
3+
// Licensed under the Mozilla Public License 2.0 <LICENSE or
4+
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
5+
// modified, or distributed except according to those terms.
6+
7+
use command::CommandResult;
8+
use erased_serde::Serialize;
9+
use errors::*;
10+
use futures::{future, Future};
11+
use host::{Host, HostType};
12+
use host::local::Local;
13+
use host::remote::Plain;
14+
use provider::Provider;
15+
use remote::{Executable, Runnable};
16+
use std::process;
17+
use super::{CommandProvider, CommandRunnable};
18+
19+
#[derive(Clone)]
20+
pub struct Generic<H: Host> {
21+
host: H
22+
}
23+
24+
struct LocalGeneric;
25+
struct RemoteGeneric;
26+
27+
#[doc(hidden)]
28+
#[derive(Serialize, Deserialize)]
29+
pub enum GenericRunnable {
30+
Available,
31+
Exec(String, Vec<String>),
32+
}
33+
34+
impl<H: Host + 'static> Provider<H> for Generic<H> {
35+
fn available(host: &H) -> Box<Future<Item = bool, Error = Error>> {
36+
match host.get_type() {
37+
HostType::Local(l) => LocalGeneric::available(l),
38+
HostType::Remote(r) => RemoteGeneric::available(r),
39+
}
40+
}
41+
42+
fn try_new(host: &H) -> Box<Future<Item = Option<Generic<H>>, Error = Error>> {
43+
let host = host.clone();
44+
Box::new(Self::available(&host)
45+
.and_then(|available| {
46+
if available {
47+
future::ok(Some(Generic { host }))
48+
} else {
49+
future::ok(None)
50+
}
51+
}))
52+
}
53+
}
54+
55+
impl<H: Host + 'static> CommandProvider<H> for Generic<H> {
56+
fn exec(&mut self, cmd: &str, shell: &[String]) -> Box<Future<Item = CommandResult, Error = Error>> {
57+
match self.host.get_type() {
58+
HostType::Local(l) => LocalGeneric::exec(l, cmd, shell),
59+
HostType::Remote(r) => RemoteGeneric::exec(r, cmd, shell),
60+
}
61+
}
62+
}
63+
64+
impl LocalGeneric {
65+
fn available(_: &Local) -> Box<Future<Item = bool, Error = Error>> {
66+
Box::new(future::ok(cfg!(unix)))
67+
}
68+
69+
fn exec(_: &Local, cmd: &str, shell: &[String]) -> Box<Future<Item = CommandResult, Error = Error>> {
70+
let cmd_owned = cmd.to_owned();
71+
let shell_owned = shell.to_owned();
72+
73+
Box::new(future::lazy(move || -> future::FutureResult<CommandResult, Error> {
74+
let (shell, shell_args) = match shell_owned.split_first() {
75+
Some((s, a)) => (s, a),
76+
None => return future::err("Invalid shell provided".into()),
77+
};
78+
79+
let out = process::Command::new(shell)
80+
.args(shell_args)
81+
.arg(&cmd_owned)
82+
.output()
83+
.chain_err(|| "Command execution failed");
84+
match out {
85+
Ok(output) => future::ok(CommandResult {
86+
success: output.status.success(),
87+
exit_code: output.status.code(),
88+
stdout: output.stdout,
89+
stderr: output.stderr,
90+
}),
91+
Err(e) => future::err(e),
92+
}
93+
}))
94+
}
95+
}
96+
97+
impl RemoteGeneric {
98+
fn available(host: &Plain) -> Box<Future<Item = bool, Error = Error>> {
99+
let runnable = Runnable::Command(
100+
CommandRunnable::Generic(
101+
GenericRunnable::Available));
102+
host.run(runnable)
103+
.chain_err(|| ErrorKind::Runnable { endpoint: "Command::Generic", func: "available" })
104+
}
105+
106+
fn exec(host: &Plain, cmd: &str, shell: &[String]) -> Box<Future<Item = CommandResult, Error = Error>> {
107+
let runnable = Runnable::Command(
108+
CommandRunnable::Generic(
109+
GenericRunnable::Exec(cmd.into(), shell.to_owned())));
110+
host.run(runnable)
111+
.chain_err(|| ErrorKind::Runnable { endpoint: "Command::Generic", func: "exec" })
112+
}
113+
}
114+
115+
impl Executable for GenericRunnable {
116+
fn exec(self, host: &Local) -> Box<Future<Item = Box<Serialize>, Error = Error>> {
117+
match self {
118+
GenericRunnable::Available => Box::new(LocalGeneric::available(host).map(|b| Box::new(b) as Box<Serialize>)),
119+
GenericRunnable::Exec(cmd, shell) => Box::new(LocalGeneric::exec(host, &cmd, &shell).map(|r| Box::new(r) as Box<Serialize>)),
120+
}
121+
}
122+
}

core/src/command/providers/mod.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,40 @@
44
// https://www.tldrlegal.com/l/mpl-2.0>. This file may not be copied,
55
// modified, or distributed except according to those terms.
66

7-
mod nix;
7+
mod generic;
88

9-
pub use self::nix::{Nix, NixRunnable};
9+
use errors::*;
10+
use erased_serde::Serialize;
11+
use futures::{future, Future};
12+
use host::Host;
13+
use host::local::Local;
14+
use provider::Provider;
15+
use remote::Executable;
16+
pub use self::generic::{Generic, GenericRunnable};
17+
use super::CommandResult;
18+
19+
pub trait CommandProvider<H: Host>: Provider<H> {
20+
fn exec(&mut self, &str, &[String]) -> Box<Future<Item = CommandResult, Error = Error>>;
21+
}
22+
23+
#[doc(hidden)]
24+
#[derive(Serialize, Deserialize)]
25+
pub enum CommandRunnable {
26+
Generic(GenericRunnable)
27+
}
28+
29+
impl Executable for CommandRunnable {
30+
fn exec(self, host: &Local) -> Box<Future<Item = Box<Serialize>, Error = Error>> {
31+
match self {
32+
CommandRunnable::Generic(p) => p.exec(host)
33+
}
34+
}
35+
}
36+
37+
pub fn factory<H: Host + 'static>(host: &H) -> Box<Future<Item = Box<CommandProvider<H>>, Error = Error>> {
38+
Box::new(Generic::try_new(host)
39+
.and_then(|opt| match opt {
40+
Some(provider) => future::ok(Box::new(provider) as Box<CommandProvider<H>>),
41+
None => future::err(ErrorKind::ProviderUnavailable("Command").into())
42+
}))
43+
}

core/src/command/providers/nix.rs

Lines changed: 0 additions & 93 deletions
This file was deleted.

0 commit comments

Comments
 (0)