Skip to content

Commit 06c51e3

Browse files
committed
Initial code for Service endpoint
1 parent f6d9a07 commit 06c51e3

File tree

11 files changed

+1069
-4
lines changed

11 files changed

+1069
-4
lines changed

core/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,11 @@ pub mod prelude {
152152
pub use host::remote::{self, Plain};
153153
pub use host::local::{self, Local};
154154
pub use package::{self, Package};
155+
pub use service::{self, Service};
155156
pub use telemetry::{self, Cpu, FsMount, LinuxDistro, Os, OsFamily, OsPlatform, Telemetry};
156157
}
157158
pub mod package;
158159
#[doc(hidden)] pub mod remote;
160+
pub mod service;
159161
mod target;
160162
pub mod telemetry;

core/src/remote.rs

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ use errors::*;
1111
use futures::{future, Future};
1212
use host::Host;
1313
use package;
14+
use service;
1415
use std::io;
15-
use telemetry;
16+
use telemetry::{self, Telemetry};
1617
use tokio_proto::streaming::{Body, Message};
1718

1819
pub type ExecutableResult = Box<Future<Item = Message<ResponseResult, Body<Vec<u8>, io::Error>>, Error = Error>>;
@@ -23,6 +24,11 @@ pub enum Request {
2324
PackageInstalled(Option<package::Provider>, String),
2425
PackageInstall(Option<package::Provider>, String),
2526
PackageUninstall(Option<package::Provider>, String),
27+
ServiceAction(Option<service::Provider>, String, String),
28+
ServiceDisable(Option<service::Provider>, String),
29+
ServiceEnable(Option<service::Provider>, String),
30+
ServiceEnabled(Option<service::Provider>, String),
31+
ServiceRunning(Option<service::Provider>, String),
2632
TelemetryLoad,
2733
}
2834

@@ -82,6 +88,46 @@ impl Executable for Request {
8288
provider.uninstall(host.handle(), &name)
8389
}
8490

91+
Request::ServiceAction(provider, name, action) => {
92+
let provider = match get_service_provider(&host.telemetry(), provider) {
93+
Ok(p) => p,
94+
Err(e) => return Box::new(future::err(e)),
95+
};
96+
provider.action(host.handle(), &name, &action)
97+
}
98+
99+
Request::ServiceEnabled(provider, name) => {
100+
let provider = match get_service_provider(&host.telemetry(), provider) {
101+
Ok(p) => p,
102+
Err(e) => return Box::new(future::err(e)),
103+
};
104+
provider.enabled(host.handle(), &name)
105+
}
106+
107+
Request::ServiceRunning(provider, name) => {
108+
let provider = match get_service_provider(&host.telemetry(), provider) {
109+
Ok(p) => p,
110+
Err(e) => return Box::new(future::err(e)),
111+
};
112+
provider.running(host.handle(), &name)
113+
}
114+
115+
Request::ServiceEnable(provider, name) => {
116+
let provider = match get_service_provider(&host.telemetry(), provider) {
117+
Ok(p) => p,
118+
Err(e) => return Box::new(future::err(e)),
119+
};
120+
provider.enable(host.handle(), &name)
121+
}
122+
123+
Request::ServiceDisable(provider, name) => {
124+
let provider = match get_service_provider(&host.telemetry(), provider) {
125+
Ok(p) => p,
126+
Err(e) => return Box::new(future::err(e)),
127+
};
128+
provider.disable(host.handle(), &name)
129+
}
130+
85131
Request::TelemetryLoad => {
86132
let provider = match telemetry::factory() {
87133
Ok(p) => p,
@@ -104,3 +150,15 @@ fn get_package_provider(name: Option<package::Provider>) -> Result<Box<package::
104150
None => package::factory(),
105151
}
106152
}
153+
154+
fn get_service_provider(telemetry: &Telemetry, name: Option<service::Provider>) -> Result<Box<service::ServiceProvider>> {
155+
match name {
156+
Some(service::Provider::Debian) => Ok(Box::new(service::Debian)),
157+
Some(service::Provider::Homebrew) => Ok(Box::new(service::Homebrew::new(telemetry))),
158+
Some(service::Provider::Launchctl) => Ok(Box::new(service::Launchctl::new(telemetry))),
159+
Some(service::Provider::Rc) => Ok(Box::new(service::Rc)),
160+
Some(service::Provider::Redhat) => Ok(Box::new(service::Redhat)),
161+
Some(service::Provider::Systemd) => Ok(Box::new(service::Systemd)),
162+
None => service::factory(telemetry),
163+
}
164+
}

core/src/service/mod.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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+
//! Endpoint for managing system services.
8+
//!
9+
//! A service is represented by the `Service` struct, which is idempotent. This
10+
//! means you can execute it repeatedly and it'll only run as needed.
11+
12+
mod providers;
13+
14+
use command::CommandStatus;
15+
use errors::*;
16+
use futures::{future, Future};
17+
use host::Host;
18+
use remote::{Request, Response};
19+
#[doc(hidden)]
20+
pub use self::providers::{
21+
factory, ServiceProvider, Debian, Homebrew, Launchctl,
22+
Rc, Redhat, Systemd
23+
};
24+
pub use self::providers::Provider;
25+
26+
/// Represents a system service to be managed for a host.
27+
///
28+
///## Example
29+
///
30+
/// Enable and start a service.
31+
///
32+
///```no_run
33+
///extern crate futures;
34+
///extern crate intecture_api;
35+
///extern crate tokio_core;
36+
///
37+
///use futures::{future, Future};
38+
///use intecture_api::errors::Error;
39+
///use intecture_api::prelude::*;
40+
///use tokio_core::reactor::Core;
41+
///
42+
///# fn main() {
43+
///let mut core = Core::new().unwrap();
44+
///let handle = core.handle();
45+
///
46+
///let host = Local::new(&handle).wait().unwrap();
47+
///
48+
///let nginx = Service::new(&host, "nginx");
49+
///let result = nginx.enable()
50+
/// .and_then(|_| {
51+
/// nginx.action("start")
52+
/// .and_then(|maybe_status| {
53+
/// match maybe_status {
54+
/// Some(status) => Box::new(status.result().unwrap().map(|_| ())) as Box<Future<Item = (), Error = Error>>,
55+
/// None => Box::new(future::ok(())),
56+
/// }
57+
/// })
58+
/// });
59+
///
60+
///core.run(result).unwrap();
61+
///# }
62+
///```
63+
pub struct Service<H: Host> {
64+
host: H,
65+
provider: Option<Provider>,
66+
name: String,
67+
}
68+
69+
impl<H: Host + 'static> Service<H> {
70+
/// Create a new `Service` with the default [`Provider`](enum.Provider.html).
71+
pub fn new(host: &H, name: &str) -> Service<H> {
72+
Service {
73+
host: host.clone(),
74+
provider: None,
75+
name: name.into(),
76+
}
77+
}
78+
79+
/// Create a new `Service` with the specified [`Provider`](enum.Provider.html).
80+
///
81+
///## Example
82+
///```
83+
///extern crate futures;
84+
///extern crate intecture_api;
85+
///extern crate tokio_core;
86+
///
87+
///use futures::Future;
88+
///use intecture_api::service::Provider;
89+
///use intecture_api::prelude::*;
90+
///use tokio_core::reactor::Core;
91+
///
92+
///# fn main() {
93+
///let mut core = Core::new().unwrap();
94+
///let handle = core.handle();
95+
///
96+
///let host = Local::new(&handle).wait().unwrap();
97+
///
98+
///Service::with_provider(&host, Provider::Systemd, "nginx");
99+
///# }
100+
pub fn with_provider(host: &H, provider: Provider, name: &str) -> Service<H> {
101+
Service {
102+
host: host.clone(),
103+
provider: Some(provider),
104+
name: name.into(),
105+
}
106+
}
107+
108+
/// Check if the service is currently running.
109+
pub fn running(&self) -> Box<Future<Item = bool, Error = Error>> {
110+
let request = Request::ServiceRunning(self.provider, self.name.clone());
111+
Box::new(self.host.request(request)
112+
.chain_err(|| ErrorKind::Request { endpoint: "Service", func: "running" })
113+
.map(|msg| {
114+
match msg.into_inner() {
115+
Response::Bool(b) => b,
116+
_ => unreachable!(),
117+
}
118+
}))
119+
}
120+
121+
/// Perform an action for the service, e.g. "start".
122+
///
123+
///## Cross-platform services
124+
///
125+
/// By design, actions are specific to a particular service and are not
126+
/// cross-platform. Actions are defined by the package maintainer that
127+
/// wrote the service configuration, thus users should take care that they
128+
/// adhere to the configuration for each platform they target.
129+
///
130+
///## Idempotence
131+
///
132+
/// This function is idempotent when running either the "start" or "stop"
133+
/// actions, as it will check first whether the service is already running.
134+
/// Idempotence is represented by the type `Future<Item = Option<..>, ...>`.
135+
/// Thus if it returns `Option::None` then the service is already in the
136+
/// required state, and if it returns `Option::Some` then Intecture is
137+
/// attempting to transition the service to the required state.
138+
///
139+
/// If this fn returns `Option::Some<..>`, the nested tuple will hold
140+
/// handles to the live output and the result of the action. Under the hood
141+
/// this reuses the `Command` endpoint, so see
142+
/// [`Command` docs](../command/struct.Command.html) for detailed
143+
/// usage.
144+
pub fn action(&self, action: &str) -> Box<Future<Item = Option<CommandStatus>, Error = Error>>
145+
{
146+
if action == "start" || action == "stop" {
147+
let host = self.host.clone();
148+
let name = self.name.clone();
149+
let provider = self.provider;
150+
let action = action.to_owned();
151+
152+
Box::new(self.running()
153+
.and_then(move |running| {
154+
if (running && action == "start") || (!running && action == "stop") {
155+
Box::new(future::ok(None)) as Box<Future<Item = _, Error = Error>>
156+
} else {
157+
Self::do_action(&host, provider, &name, &action)
158+
}
159+
}))
160+
} else {
161+
Self::do_action(&self.host, self.provider, &self.name, action)
162+
}
163+
}
164+
165+
fn do_action(host: &H, provider: Option<Provider>, name: &str, action: &str)
166+
-> Box<Future<Item = Option<CommandStatus>, Error = Error>>
167+
{
168+
let request = Request::ServiceAction(provider, name.into(), action.into());
169+
Box::new(host.request(request)
170+
.chain_err(|| ErrorKind::Request { endpoint: "Service", func: "action" })
171+
.map(|msg| Some(CommandStatus::new(msg))))
172+
}
173+
174+
/// Check if the service will start at boot.
175+
pub fn enabled(&self) -> Box<Future<Item = bool, Error = Error>> {
176+
let request = Request::ServiceEnabled(self.provider, self.name.clone());
177+
Box::new(self.host.request(request)
178+
.chain_err(|| ErrorKind::Request { endpoint: "Service", func: "enabled" })
179+
.map(|msg| {
180+
match msg.into_inner() {
181+
Response::Bool(b) => b,
182+
_ => unreachable!(),
183+
}
184+
}))
185+
}
186+
187+
/// Instruct the service to start at boot.
188+
///
189+
///## Idempotence
190+
///
191+
/// This function is idempotent, which is represented by the type
192+
/// `Future<Item = Option<..>, ...>`. Thus if it returns `Option::None`
193+
/// then the service is already enabled, and if it returns `Option::Some`
194+
/// then Intecture is attempting to enable the service.
195+
///
196+
/// If this fn returns `Option::Some<..>`, the nested tuple will hold
197+
/// handles to the live output and the 'enable' command result. Under
198+
/// the hood this reuses the `Command` endpoint, so see
199+
/// [`Command` docs](../command/struct.Command.html) for detailed
200+
/// usage.
201+
pub fn enable(&self) -> Box<Future<Item = Option<()>, Error = Error>>
202+
{
203+
let host = self.host.clone();
204+
let provider = self.provider;
205+
let name = self.name.clone();
206+
207+
Box::new(self.enabled()
208+
.and_then(move |enabled| {
209+
if enabled {
210+
Box::new(future::ok(None)) as Box<Future<Item = _, Error = Error>>
211+
} else {
212+
let request = Request::ServiceEnable(provider, name);
213+
Box::new(host.request(request)
214+
.chain_err(|| ErrorKind::Request { endpoint: "Service", func: "enable" })
215+
.map(|msg| match msg.into_inner() {
216+
Response::Null => Some(()),
217+
_ => unreachable!(),
218+
}))
219+
}
220+
}))
221+
}
222+
223+
/// Prevent the service from starting at boot.
224+
///
225+
///## Idempotence
226+
///
227+
/// This function is idempotent, which is represented by the type
228+
/// `Future<Item = Option<..>, ...>`. Thus if it returns `Option::None`
229+
/// then the service is already disabled, and if it returns `Option::Some`
230+
/// then Intecture is attempting to disable the service.
231+
///
232+
/// If this fn returns `Option::Some<..>`, the nested tuple will hold
233+
/// handles to the live output and the 'disable' command result. Under
234+
/// the hood this reuses the `Command` endpoint, so see
235+
/// [`Command` docs](../command/struct.Command.html) for detailed
236+
/// usage.
237+
pub fn disable(&self) -> Box<Future<Item = Option<()>, Error = Error>>
238+
{
239+
let host = self.host.clone();
240+
let provider = self.provider;
241+
let name = self.name.clone();
242+
243+
Box::new(self.enabled()
244+
.and_then(move |enabled| {
245+
if enabled {
246+
let request = Request::ServiceDisable(provider, name);
247+
Box::new(host.request(request)
248+
.chain_err(|| ErrorKind::Request { endpoint: "Service", func: "disable" })
249+
.map(|msg| match msg.into_inner() {
250+
Response::Null => Some(()),
251+
_ => unreachable!(),
252+
}))
253+
} else {
254+
Box::new(future::ok(None)) as Box<Future<Item = _, Error = Error>>
255+
}
256+
}))
257+
}
258+
}

0 commit comments

Comments
 (0)