summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShaun Taheri <github@taheris.co.uk>2016-09-29 16:55:47 +0200
committerGitHub <noreply@github.com>2016-09-29 16:55:47 +0200
commita993b143fec9dee763366eba36681a276c4d47e7 (patch)
tree8d549735db8e9cd8c221999eb11d69b1cded38c8
parent484e98981f5ddbf61a9e4ca6190c9f2c2fcdec4c (diff)
parentb4d263c28fbc408d6dc2a437bd4a4affd5b6072e (diff)
downloadrvi_sota_client-a993b143fec9dee763366eba36681a276c4d47e7.tar.gz
Merge pull request #126 from advancedtelematic/feat/pro-1374/log-http-body-error
Return the HTTP Body when available
-rwxr-xr-xrun/system_info.sh2
-rw-r--r--src/datatype/auth.rs29
-rw-r--r--src/datatype/command.rs16
-rw-r--r--src/datatype/error.rs9
-rw-r--r--src/datatype/json_rpc.rs10
-rw-r--r--src/datatype/mod.rs2
-rw-r--r--src/gateway/http.rs10
-rw-r--r--src/http/auth_client.rs162
-rw-r--r--src/http/http_client.rs44
-rw-r--r--src/http/mod.rs2
-rw-r--r--src/http/test_client.rs10
-rw-r--r--src/interpreter.rs20
-rw-r--r--src/oauth2.rs11
-rw-r--r--src/package_manager/rpm.rs2
-rw-r--r--src/sota.rs49
15 files changed, 224 insertions, 154 deletions
diff --git a/run/system_info.sh b/run/system_info.sh
index ccbca86..e47ef4a 100755
--- a/run/system_info.sh
+++ b/run/system_info.sh
@@ -14,4 +14,4 @@ if [ -f $SERVICE_HOSTNAME_PATH ]; then
INFO=$(echo $SERVICE_HOSTNAME $INFO | jq -s add | jq -r .)
fi
-echo $INFO | jq -r .
+echo $INFO | jq -c -r .
diff --git a/src/datatype/auth.rs b/src/datatype/auth.rs
index cbfd097..83c872a 100644
--- a/src/datatype/auth.rs
+++ b/src/datatype/auth.rs
@@ -5,7 +5,7 @@ use std::borrow::Cow;
#[derive(Clone, Debug)]
pub enum Auth {
None,
- Credentials(ClientId, ClientSecret),
+ Credentials(ClientCredentials),
Token(AccessToken),
}
@@ -16,8 +16,15 @@ impl<'a> Into<Cow<'a, Auth>> for Auth {
}
-/// For storage of the returned access token data following a successful
-/// authentication.
+/// Encapsulates the client id and secret used during authentication.
+#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)]
+pub struct ClientCredentials {
+ pub client_id: String,
+ pub client_secret: String,
+}
+
+
+/// Stores the returned access token data following a successful authentication.
#[derive(RustcDecodable, Debug, PartialEq, Clone, Default)]
pub struct AccessToken {
pub access_token: String,
@@ -31,19 +38,3 @@ impl<'a> Into<Cow<'a, AccessToken>> for AccessToken {
Cow::Owned(self)
}
}
-
-
-/// Encapsulates a `String` type for use in `Auth::Credentials`
-#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)]
-pub struct ClientId(pub String);
-
-/// Encapsulates a `String` type for use in `Auth::Credentials`
-#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)]
-pub struct ClientSecret(pub String);
-
-/// Encapsulates the client id and secret used during authentication.
-#[derive(Clone, PartialEq, Eq, Debug, RustcEncodable, RustcDecodable)]
-pub struct ClientCredentials {
- pub client_id: ClientId,
- pub client_secret: ClientSecret,
-}
diff --git a/src/datatype/command.rs b/src/datatype/command.rs
index 71567da..c88e8d5 100644
--- a/src/datatype/command.rs
+++ b/src/datatype/command.rs
@@ -3,8 +3,8 @@ use std::str;
use std::str::FromStr;
use nom::{IResult, space, eof};
-use datatype::{ClientCredentials, ClientId, ClientSecret, Error, InstalledSoftware,
- Package, UpdateReport, UpdateRequestId, UpdateResultCode};
+use datatype::{ClientCredentials, Error, InstalledSoftware, Package, UpdateReport,
+ UpdateRequestId, UpdateResultCode};
/// System-wide commands that are sent to the interpreter.
@@ -111,8 +111,9 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result<Command, Error> {
0 => Ok(Command::Authenticate(None)),
1 => Err(Error::Command("usage: auth <client-id> <client-secret>".to_string())),
2 => Ok(Command::Authenticate(Some(ClientCredentials {
- client_id: ClientId(args[0].to_string()),
- client_secret: ClientSecret(args[1].to_string())}))),
+ client_id: args[0].to_string(),
+ client_secret: args[1].to_string()
+ }))),
_ => Err(Error::Command(format!("unexpected Authenticate args: {:?}", args))),
},
@@ -192,8 +193,7 @@ fn parse_arguments(cmd: Command, args: Vec<&str>) -> Result<Command, Error> {
#[cfg(test)]
mod tests {
use super::{command, arguments};
- use datatype::{Command, ClientCredentials, ClientId, ClientSecret, Package,
- UpdateReport, UpdateResultCode};
+ use datatype::{Command, ClientCredentials, Package, UpdateReport, UpdateResultCode};
use nom::IResult;
@@ -224,8 +224,8 @@ mod tests {
assert_eq!("auth".parse::<Command>().unwrap(), Command::Authenticate(None));
assert_eq!("auth user pass".parse::<Command>().unwrap(),
Command::Authenticate(Some(ClientCredentials {
- client_id: ClientId("user".to_string()),
- client_secret: ClientSecret("pass".to_string()),
+ client_id: "user".to_string(),
+ client_secret: "pass".to_string(),
})));
assert!("auth one".parse::<Command>().is_err());
assert!("auth one two three".parse::<Command>().is_err());
diff --git a/src/datatype/error.rs b/src/datatype/error.rs
index 8267234..bb0ab4e 100644
--- a/src/datatype/error.rs
+++ b/src/datatype/error.rs
@@ -13,7 +13,7 @@ use toml::{ParserError as TomlParserError, DecodeError as TomlDecodeError};
use url::ParseError as UrlParseError;
use datatype::Event;
-use http::auth_client::AuthHandler;
+use http::{AuthHandler, ResponseData};
use gateway::Interpret;
use ws::Error as WebsocketError;
@@ -21,10 +21,11 @@ use ws::Error as WebsocketError;
/// System-wide errors that are returned from `Result` type failures.
#[derive(Debug)]
pub enum Error {
- Authorization(String),
Client(String),
Command(String),
FromUtf8(FromUtf8Error),
+ Http(ResponseData),
+ HttpAuth(ResponseData),
Hyper(HyperError),
HyperClient(HyperClientError<AuthHandler>),
Io(IoError),
@@ -76,6 +77,7 @@ derive_from!([
JsonEncoderError => JsonEncoder,
JsonDecoderError => JsonDecoder,
RecvError => Recv,
+ ResponseData => Http,
TomlDecodeError => TomlDecode,
UrlParseError => UrlParse,
WebsocketError => Websocket
@@ -92,9 +94,10 @@ impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> FmtResult {
let inner: String = match *self {
Error::Client(ref s) => format!("Http client error: {}", s.clone()),
- Error::Authorization(ref s) => format!("Http client authorization error: {}", s.clone()),
Error::Command(ref e) => format!("Unknown Command: {}", e.clone()),
Error::FromUtf8(ref e) => format!("From utf8 error: {}", e.clone()),
+ Error::Http(ref r) => format!("HTTP client error: {}", r.clone()),
+ Error::HttpAuth(ref r) => format!("HTTP authorization error: {}", r.clone()),
Error::Hyper(ref e) => format!("Hyper error: {}", e.clone()),
Error::HyperClient(ref e) => format!("Hyper client error: {}", e.clone()),
Error::Io(ref e) => format!("IO error: {}", e.clone()),
diff --git a/src/datatype/json_rpc.rs b/src/datatype/json_rpc.rs
index 3eed9a2..e3a046a 100644
--- a/src/datatype/json_rpc.rs
+++ b/src/datatype/json_rpc.rs
@@ -1,7 +1,7 @@
use rustc_serialize::{json, Decodable, Encodable};
use time;
-use http::{AuthClient, Client};
+use http::{AuthClient, Client, Response};
use super::Url;
@@ -32,8 +32,12 @@ impl<E: Encodable> RpcRequest<E> {
let body = json::encode(self).expect("couldn't encode RpcRequest");
let resp_rx = client.post(url, Some(body.into_bytes()));
let resp = resp_rx.recv().expect("no RpcRequest response received");
- let data = try!(resp.map_err(|err| format!("{}", err)));
- String::from_utf8(data).map_err(|err| format!("{}", err))
+
+ match resp {
+ Response::Success(data) => String::from_utf8(data.body).or_else(|err| Err(format!("{}", err))),
+ Response::Failed(data) => Err(format!("{}", data)),
+ Response::Error(err) => Err(format!("{}", err))
+ }
}
}
diff --git a/src/datatype/mod.rs b/src/datatype/mod.rs
index 6516422..017868c 100644
--- a/src/datatype/mod.rs
+++ b/src/datatype/mod.rs
@@ -10,7 +10,7 @@ pub mod update_report;
pub mod update_request;
pub mod url;
-pub use self::auth::{AccessToken, Auth, ClientId, ClientSecret, ClientCredentials};
+pub use self::auth::{AccessToken, Auth, ClientCredentials};
pub use self::command::Command;
pub use self::config::{AuthConfig, CoreConfig, Config, DBusConfig, DeviceConfig,
GatewayConfig, RviConfig};
diff --git a/src/gateway/http.rs b/src/gateway/http.rs
index 6ccc2b5..f397630 100644
--- a/src/gateway/http.rs
+++ b/src/gateway/http.rs
@@ -91,7 +91,7 @@ mod tests {
use super::*;
use gateway::{Gateway, Interpret};
use datatype::{Command, Event};
- use http::{AuthClient, Client, set_ca_certificates};
+ use http::{AuthClient, Client, Response, set_ca_certificates};
#[test]
@@ -124,8 +124,12 @@ mod tests {
let url = "http://127.0.0.1:8888".parse().unwrap();
let body = json::encode(&cmd).unwrap();
let resp_rx = client.post(url, Some(body.into_bytes()));
- let resp = resp_rx.recv().unwrap().unwrap();
- let text = String::from_utf8(resp).unwrap();
+ let resp = resp_rx.recv().unwrap();
+ let text = match resp {
+ Response::Success(data) => String::from_utf8(data.body).unwrap(),
+ Response::Failed(data) => panic!("failed response: {}", data),
+ Response::Error(err) => panic!("error response: {}", err)
+ };
assert_eq!(json::decode::<Event>(&text).unwrap(),
Event::FoundSystemInfo(format!("{}", id)));
});
diff --git a/src/http/auth_client.rs b/src/http/auth_client.rs
index d9e28cb..2e26464 100644
--- a/src/http/auth_client.rs
+++ b/src/http/auth_client.rs
@@ -14,7 +14,7 @@ use std::time::Duration;
use time;
use datatype::{Auth, Error};
-use http::{Client, get_openssl, Request, Response};
+use http::{Client, get_openssl, Request, Response, ResponseData};
/// The `AuthClient` will attach an `Authentication` header to each outgoing
@@ -52,58 +52,30 @@ impl Client for AuthClient {
fn chan_request(&self, req: Request, resp_tx: Sender<Response>) {
info!("{} {}", req.method, req.url);
let _ = self.client.request(req.url.inner(), AuthHandler {
- auth: self.auth.clone(),
- req: req,
- timeout: Duration::from_secs(20),
- started: None,
- written: 0,
- response: Vec::new(),
- resp_tx: resp_tx.clone(),
- }).map_err(|err| resp_tx.send(Err(Error::from(err))));
+ auth: self.auth.clone(),
+ req: req,
+ timeout: Duration::from_secs(20),
+ started: None,
+ written: 0,
+ resp_code: StatusCode::InternalServerError,
+ resp_body: Vec::new(),
+ resp_tx: resp_tx.clone(),
+ }).map_err(|err| resp_tx.send(Response::Error(Error::from(err))));
}
}
/// The async handler for outgoing HTTP requests.
-// FIXME: uncomment when yocto is at 1.8.0: #[derive(Debug)]
+#[derive(Debug)]
pub struct AuthHandler {
- auth: Auth,
- req: Request,
- timeout: Duration,
- started: Option<u64>,
- written: usize,
- response: Vec<u8>,
- resp_tx: Sender<Response>,
-}
-
-// FIXME: required for building on 1.7.0 only
-impl ::std::fmt::Debug for AuthHandler {
- fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
- write!(f, "unimplemented")
- }
-}
-
-impl AuthHandler {
- fn redirect_request(&mut self, resp: HyperResponse) {
- match resp.headers().get::<Location>() {
- Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| {
- debug!("redirecting to {}", url);
- // drop Authorization Header on redirect
- let client = AuthClient::default();
- let resp_rx = client.send_request(Request {
- url: url,
- method: self.req.method.clone(),
- body: mem::replace(&mut self.req.body, None),
- });
- match resp_rx.recv().expect("no redirect_request response") {
- Ok(data) => self.resp_tx.send(Ok(data)),
- Err(err) => self.resp_tx.send(Err(Error::from(err)))
- }
- }).unwrap_or_else(|err| self.resp_tx.send(Err(Error::from(err)))),
-
- None => self.resp_tx.send(Err(Error::Client("redirect missing Location header".to_string())))
- }
- }
+ auth: Auth,
+ req: Request,
+ timeout: Duration,
+ started: Option<u64>,
+ written: usize,
+ resp_code: StatusCode,
+ resp_body: Vec<u8>,
+ resp_tx: Sender<Response>,
}
/// The `AuthClient` may be used for both HTTP and HTTPS connections.
@@ -125,15 +97,12 @@ impl Handler<Stream> for AuthHandler {
headers.set(ContentType(mime_json));
}
- Auth::Credentials(_, _) if self.req.body.is_some() => {
- panic!("no request body expected for Auth::Credentials");
- }
-
- Auth::Credentials(ref id, ref secret) => {
- headers.set(Authorization(Basic { username: id.0.clone(),
- password: Some(secret.0.clone()) }));
+ Auth::Credentials(ref cred) => {
+ headers.set(Authorization(Basic {
+ username: cred.client_id.clone(),
+ password: Some(cred.client_secret.clone())
+ }));
headers.set(ContentType(mime_form));
- self.req.body = Some(br#"grant_type=client_credentials"#.to_vec());
}
Auth::Token(ref token) => {
@@ -173,7 +142,7 @@ impl Handler<Stream> for AuthHandler {
Err(err) => {
error!("unable to write request body: {}", err);
- self.resp_tx.send(Err(Error::from(err)));
+ self.resp_tx.send(Response::Error(Error::from(err)));
Next::remove()
}
}
@@ -186,32 +155,34 @@ impl Handler<Stream> for AuthHandler {
let latency = time::precise_time_ns() as f64 - started as f64;
debug!("on_response latency: {}ms", (latency / 1e6) as u32);
- if resp.status().is_success() {
- if let Some(len) = resp.headers().get::<ContentLength>() {
- if **len > 0 {
- return Next::read();
- }
- }
- self.resp_tx.send(Ok(Vec::new()));
- Next::end()
- } else if resp.status().is_redirection() {
+ self.resp_code = *resp.status();
+ if resp.status().is_redirection() {
self.redirect_request(resp);
Next::end()
- } else if resp.status() == &StatusCode::Unauthorized
- || resp.status() == &StatusCode::Forbidden {
- self.resp_tx.send(Err(Error::Authorization(format!("{}", resp.status()))));
+ } else if let None = resp.headers().get::<ContentLength>() {
Next::end()
} else {
- self.resp_tx.send(Err(Error::Client(format!("{}", resp.status()))));
- Next::end()
+ Next::read()
}
}
fn on_response_readable(&mut self, decoder: &mut Decoder<Stream>) -> Next {
- match io::copy(decoder, &mut self.response) {
+ match io::copy(decoder, &mut self.resp_body) {
Ok(0) => {
- debug!("on_response_readable bytes read: {}", self.response.len());
- self.resp_tx.send(Ok(mem::replace(&mut self.response, Vec::new())));
+ debug!("on_response_readable body size: {}", self.resp_body.len());
+ let resp = ResponseData {
+ code: self.resp_code,
+ body: mem::replace(&mut self.resp_body, Vec::new())
+ };
+
+ if resp.code == StatusCode::Unauthorized || resp.code == StatusCode::Forbidden {
+ self.resp_tx.send(Response::Error(Error::HttpAuth(resp)));
+ } else if resp.code.is_success() {
+ self.resp_tx.send(Response::Success(resp));
+ } else {
+ self.resp_tx.send(Response::Failed(resp));
+ }
+
Next::end()
}
@@ -227,7 +198,7 @@ impl Handler<Stream> for AuthHandler {
Err(err) => {
error!("unable to read response body: {}", err);
- self.resp_tx.send(Err(Error::from(err)));
+ self.resp_tx.send(Response::Error(Error::from(err)));
Next::end()
}
}
@@ -235,11 +206,31 @@ impl Handler<Stream> for AuthHandler {
fn on_error(&mut self, err: hyper::Error) -> Next {
error!("on_error: {}", err);
- self.resp_tx.send(Err(Error::from(err)));
+ self.resp_tx.send(Response::Error(Error::from(err)));
Next::remove()
}
}
+impl AuthHandler {
+ fn redirect_request(&mut self, resp: HyperResponse) {
+ match resp.headers().get::<Location>() {
+ Some(&Location(ref loc)) => self.req.url.join(loc).map(|url| {
+ debug!("redirecting to {}", url);
+ // drop Authorization Header on redirect
+ let client = AuthClient::default();
+ let resp_rx = client.send_request(Request {
+ url: url,
+ method: self.req.method.clone(),
+ body: mem::replace(&mut self.req.body, None),
+ });
+ self.resp_tx.send(resp_rx.recv().expect("no redirect_request response"))
+ }).unwrap_or_else(|err| self.resp_tx.send(Response::Error(Error::from(err)))),
+
+ None => self.resp_tx.send(Response::Error((Error::Client("redirect missing Location header".to_string()))))
+ }
+ }
+}
+
#[cfg(test)]
mod tests {
@@ -247,7 +238,7 @@ mod tests {
use std::path::Path;
use super::*;
- use http::{Client, set_ca_certificates};
+ use http::{Client, Response, set_ca_certificates};
fn get_client() -> AuthClient {
@@ -260,8 +251,13 @@ mod tests {
let client = get_client();
let url = "http://eu.httpbin.org/bytes/16?seed=123".parse().unwrap();
let resp_rx = client.get(url, None);
- let data = resp_rx.recv().unwrap().unwrap();
- assert_eq!(data, vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22]);
+ let resp = resp_rx.recv().unwrap();
+ let expect = vec![13, 22, 104, 27, 230, 9, 137, 85, 218, 40, 86, 85, 62, 0, 111, 22];
+ match resp {
+ Response::Success(data) => assert_eq!(data.body, expect),
+ Response::Failed(data) => panic!("failed response: {}", data),
+ Response::Error(err) => panic!("error response: {}", err)
+ };
}
#[test]
@@ -269,9 +265,13 @@ mod tests {
let client = get_client();
let url = "https://eu.httpbin.org/post".parse().unwrap();
let resp_rx = client.post(url, Some(br#"foo"#.to_vec()));
- let body = resp_rx.recv().unwrap().unwrap();
- let resp = String::from_utf8(body).unwrap();
- let json = Json::from_str(&resp).unwrap();
+ let resp = resp_rx.recv().unwrap();
+ let body = match resp {
+ Response::Success(data) => String::from_utf8(data.body).unwrap(),
+ Response::Failed(data) => panic!("failed response: {}", data),
+ Response::Error(err) => panic!("error response: {}", err)
+ };
+ let json = Json::from_str(&body).unwrap();
let obj = json.as_object().unwrap();
let data = obj.get("data").unwrap().as_string().unwrap();
assert_eq!(data, "foo");
diff --git a/src/http/http_client.rs b/src/http/http_client.rs
index 492166c..b911b8d 100644
--- a/src/http/http_client.rs
+++ b/src/http/http_client.rs
@@ -1,5 +1,8 @@
use chan;
use chan::{Sender, Receiver};
+use hyper::status::StatusCode;
+use std::fmt::{Display, Formatter, Result as FmtResult};
+use std::str;
use datatype::{Error, Method, Url};
@@ -39,5 +42,42 @@ pub struct Request {
pub body: Option<Vec<u8>>
}
-/// Return the body of an HTTP response on success, or an `Error` otherwise.
-pub type Response = Result<Vec<u8>, Error>;
+
+/// A Response enumerates between a successful (e.g. 2xx) HTTP response, a failed
+/// (e.g. 4xx/5xx) response, or an Error before receiving any response.
+#[derive(Debug)]
+pub enum Response {
+ Success(ResponseData),
+ Failed(ResponseData),
+ Error(Error)
+}
+
+impl Display for Response {
+ fn fmt(&self, f: &mut Formatter) -> FmtResult {
+ match *self {
+ Response::Success(ref data) => write!(f, "{}", data),
+ Response::Failed(ref data) => write!(f, "{}", data),
+ Response::Error(ref err) => write!(f, "{}", err),
+ }
+ }
+}
+
+
+/// Wraps the HTTP Status Code as well as any returned body.
+#[derive(Debug)]
+pub struct ResponseData {
+ pub code: StatusCode,
+ pub body: Vec<u8>
+}
+
+impl Display for ResponseData {
+ fn fmt(&self, f: &mut Formatter) -> FmtResult {
+ match self.body.len() {
+ 0 => write!(f, "Response Code: {}", self.code),
+ n => match str::from_utf8(&self.body) {
+ Ok(text) => write!(f, "Response Code: {}, Body:\n{}", self.code, text),
+ Err(_) => write!(f, "Response Code: {}, Body: {} bytes", self.code, n),
+ }
+ }
+ }
+}
diff --git a/src/http/mod.rs b/src/http/mod.rs
index 5e990a3..11b1e3a 100644
--- a/src/http/mod.rs
+++ b/src/http/mod.rs
@@ -5,7 +5,7 @@ pub mod openssl;
pub mod test_client;
pub use self::auth_client::{AuthClient, AuthHandler};
-pub use self::http_client::{Client, Request, Response};
+pub use self::http_client::{Client, Request, Response, ResponseData};
pub use self::http_server::{Server, ServerHandler};
pub use self::openssl::{get_openssl, set_ca_certificates};
pub use self::test_client::TestClient;
diff --git a/src/http/test_client.rs b/src/http/test_client.rs
index 7857e0f..1886fdf 100644
--- a/src/http/test_client.rs
+++ b/src/http/test_client.rs
@@ -1,8 +1,9 @@
use chan::Sender;
+use hyper::status::StatusCode;
use std::cell::RefCell;
use datatype::Error;
-use http::{Client, Request, Response};
+use http::{Client, Request, Response, ResponseData};
/// The `TestClient` will return HTTP responses from an existing list of strings.
@@ -26,8 +27,11 @@ impl TestClient {
impl Client for TestClient {
fn chan_request(&self, req: Request, resp_tx: Sender<Response>) {
match self.responses.borrow_mut().pop() {
- Some(body) => resp_tx.send(Ok(body.as_bytes().to_vec())),
- None => resp_tx.send(Err(Error::Client(req.url.to_string())))
+ Some(body) => resp_tx.send(Response::Success(ResponseData {
+ code: StatusCode::Ok,
+ body: body.as_bytes().to_vec()
+ })),
+ None => resp_tx.send(Response::Error(Error::Client(req.url.to_string())))
}
}
diff --git a/src/interpreter.rs b/src/interpreter.rs
index 75e6e8b..00ad37c 100644
--- a/src/interpreter.rs
+++ b/src/interpreter.rs
@@ -3,9 +3,8 @@ use chan::{Sender, Receiver};
use std;
use std::borrow::Cow;
-use datatype::{AccessToken, Auth, ClientId, ClientSecret, Command, Config,
- Error, Event, Package, UpdateReport, UpdateRequestStatus as Status,
- UpdateResultCode};
+use datatype::{AccessToken, Auth, ClientCredentials, Command, Config, Error, Event,
+ Package, UpdateReport, UpdateRequestStatus as Status, UpdateResultCode};
use gateway::Interpret;
use http::{AuthClient, Client};
use oauth2::authenticate;
@@ -38,13 +37,12 @@ impl Interpreter<Event, Command> for EventInterpreter {
info!("Event received: {}", event);
match event {
Event::Authenticated => {
- ctx.send(Command::SendSystemInfo);
-
if self.package_manager != PackageManager::Off {
self.package_manager.installed_packages().map(|packages| {
ctx.send(Command::SendInstalledPackages(packages));
}).unwrap_or_else(|err| error!("couldn't send a list of packages: {}", err));
}
+ ctx.send(Command::SendSystemInfo);
}
Event::NotAuthenticated => {
@@ -141,24 +139,22 @@ impl<'t> Interpreter<Interpret, Event> for GlobalInterpreter<'t> {
etx.send(ev.clone());
response_ev = Some(ev);
}
- info!("Interpreter finished.");
}
- Err(Error::Authorization(_)) => {
+ Err(Error::HttpAuth(_)) => {
let ev = Event::NotAuthenticated;
etx.send(ev.clone());
response_ev = Some(ev);
- error!("Interpreter authentication failed");
}
Err(err) => {
let ev = Event::Error(format!("{}", err));
etx.send(ev.clone());
response_ev = Some(ev);
- error!("Interpreter failed: {}", err);
}
}
+ info!("Interpreter finished.");
let ev = response_ev.expect("no response event to send back");
interpret.response_tx.map(|tx| tx.lock().unwrap().send(ev));
}
@@ -250,8 +246,10 @@ impl<'t> GlobalInterpreter<'t> {
match cmd {
Command::Authenticate(_) => {
let config = self.config.auth.clone().expect("trying to authenticate without auth config");
- self.set_client(Auth::Credentials(ClientId(config.client_id),
- ClientSecret(config.client_secret)));
+ self.set_client(Auth::Credentials(ClientCredentials {
+ client_id: config.client_id,
+ client_secret: config.client_secret,
+ }));
let server = config.server.join("/token").expect("couldn't build authentication url");
let token = try!(authenticate(server, self.http_client.as_ref()));
self.set_client(Auth::Token(token.clone()));
diff --git a/src/oauth2.rs b/src/oauth2.rs
index 0c5f152..e34e4c2 100644
--- a/src/oauth2.rs
+++ b/src/oauth2.rs
@@ -1,16 +1,19 @@
use rustc_serialize::json;
use datatype::{AccessToken, Error, Url};
-use http::Client;
+use http::{Client, Response};
/// Authenticate with the specified OAuth2 server to retrieve a new `AccessToken`.
pub fn authenticate(server: Url, client: &Client) -> Result<AccessToken, Error> {
debug!("authenticating at {}", server);
- let resp_rx = client.post(server, None);
+ let resp_rx = client.post(server, Some(br#"grant_type=client_credentials"#.to_vec()));
let resp = resp_rx.recv().expect("no authenticate response received");
- let data = try!(resp);
- let body = try!(String::from_utf8(data));
+ let body = match resp {
+ Response::Success(data) => try!(String::from_utf8(data.body)),
+ Response::Failed(data) => return Err(Error::from(data)),
+ Response::Error(err) => return Err(err)
+ };
Ok(try!(json::decode(&body)))
}
diff --git a/src/package_manager/rpm.rs b/src/package_manager/rpm.rs
index 99aacbf..beab7dd 100644
--- a/src/package_manager/rpm.rs
+++ b/src/package_manager/rpm.rs
@@ -23,7 +23,7 @@ pub fn installed_packages() -> Result<Vec<Package>, Error> {
})
}
-/// Installs a new RPM package.
+/// Installs a new RPM package with `rpm -Uvh --force <package-path>`.
pub fn install_package(path: &str) -> Result<InstallOutcome, InstallOutcome> {
let output = try!(Command::new("rpm").arg("-Uvh").arg("--force").arg(path)
.output()
diff --git a/src/sota.rs b/src/sota.rs
index 6c48424..9ea615e 100644
--- a/src/sota.rs
+++ b/src/sota.rs
@@ -5,7 +5,7 @@ use std::path::PathBuf;
use datatype::{Config, DeviceReport, DownloadComplete, Error, Package,
UpdateReport, UpdateRequest, UpdateRequestId, Url};
-use http::Client;
+use http::{Client, Response};
/// Encapsulate the client configuration and HTTP client used for
@@ -38,20 +38,31 @@ impl<'c, 'h> Sota<'c, 'h> {
/// Query the Core server for any pending or in-flight package updates.
pub fn get_update_requests(&mut self) -> Result<Vec<UpdateRequest>, Error> {
- let _ = self.client.get(self.endpoint(""), None); // FIXME(PRO-1352): single endpoint
let resp_rx = self.client.get(self.endpoint("/queued"), None);
let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't get new updates".to_string())));
- let text = try!(String::from_utf8(try!(resp)));
+ let data = match resp {
+ Response::Success(data) => data,
+ Response::Failed(data) => return Err(Error::from(data)),
+ Response::Error(err) => return Err(err)
+ };
+
+ let text = try!(String::from_utf8(data.body));
Ok(try!(json::decode::<Vec<UpdateRequest>>(&text)))
}
/// Download a specific update from the Core server.
pub fn download_update(&mut self, id: UpdateRequestId) -> Result<DownloadComplete, Error> {
- let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None);
- let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string())));
+ let resp_rx = self.client.get(self.endpoint(&format!("/{}/download", id)), None);
+ let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't download update".to_string())));
+ let data = match resp {
+ Response::Success(data) => data,
+ Response::Failed(data) => return Err(Error::from(data)),
+ Response::Error(err) => return Err(err)
+ };
+
let path = try!(self.package_path(id.clone()));
let mut file = try!(File::create(&path));
- let _ = io::copy(&mut &*try!(resp), &mut file);
+ let _ = io::copy(&mut &*data.body, &mut file);
Ok(DownloadComplete {
update_id: id,
update_image: path.to_string(),
@@ -75,8 +86,12 @@ impl<'c, 'h> Sota<'c, 'h> {
let body = try!(json::encode(packages));
let resp_rx = self.client.put(self.endpoint("/installed"), Some(body.into_bytes()));
let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send installed packages".to_string())));
- let _ = try!(resp);
- Ok(())
+
+ match resp {
+ Response::Success(_) => Ok(()),
+ Response::Failed(data) => Err(Error::from(data)),
+ Response::Error(err) => Err(err)
+ }
}
/// Send the outcome of a package update to the Core server.
@@ -86,16 +101,24 @@ impl<'c, 'h> Sota<'c, 'h> {
let url = self.endpoint(&format!("/{}", report.device));
let resp_rx = self.client.post(url, Some(body.into_bytes()));
let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send update report".to_string())));
- let _ = try!(resp);
- Ok(())
+
+ match resp {
+ Response::Success(_) => Ok(()),
+ Response::Failed(data) => Err(Error::from(data)),
+ Response::Error(err) => Err(err)
+ }
}
/// Send system information from the device to the Core server.
pub fn send_system_info(&mut self, body: &str) -> Result<(), Error> {
let resp_rx = self.client.put(self.endpoint("/system_info"), Some(body.as_bytes().to_vec()));
let resp = try!(resp_rx.recv().ok_or(Error::Client("couldn't send system info".to_string())));
- let _ = try!(resp);
- Ok(())
+
+ match resp {
+ Response::Success(_) => Ok(()),
+ Response::Failed(data) => Err(Error::from(data)),
+ Response::Error(err) => Err(err)
+ }
}
}
@@ -125,7 +148,7 @@ mod tests {
let json = format!("[{}]", json::encode(&pending_update).unwrap());
let mut sota = Sota {
config: &Config::default(),
- client: &mut TestClient::from(vec![json.to_string(), "[]".to_string()]),
+ client: &mut TestClient::from(vec![json.to_string()]),
};
let updates: Vec<UpdateRequest> = sota.get_update_requests().unwrap();