diff --git a/Cargo.toml b/Cargo.toml index e3993a3..2df32ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chef" -version = "0.1.0" +version = "0.2.0" authors = ["Thom May "] readme = "README.md" description = "Models for Chef Server objects" @@ -11,17 +11,23 @@ edition = "2018" [dependencies] chef_api = { version = "0.2", path = "chef_api" } clippy = {version = "0", optional = true} -env_logger = "0.4" +env_logger = "0.10" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" -log = "0.3" +log = "0.4" chrono = "0.4" failure = "0.1" +[dev-dependencies] +serde = { version = "1.0", features = ["derive"]} + [features] default = [] dev = ["clippy"] [workspace] members = [ "chef_api" ] + +[package.metadata.release] +publish = false diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index 5ec24f3..fc2e76b 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -9,24 +9,26 @@ license = "Apache-2.0" edition = "2018" [dependencies] -time = "0.1" -log = "0.3" -rustc-serialize = "0.3" +time = "0.3" +log = "0.4" -futures = "0.1" -tokio-core = "0.1" -hyper = "0.11" -hyper-openssl = "0.5" +futures = "0.3" +tokio = { version = "1", features = [ "rt", "rt-multi-thread" ]} +hyper = { version = "0.14", features = [ "client", "http1" ]} +hyper-openssl = "0.9" -url = "1.6" +url = "2" chrono = "0.4" openssl = "0.10" -env_logger = "0.4" +env_logger = "0.10" serde = "1.0" serde_derive = "1.0" serde_json = "1.0" failure = "0.1" -toml = "0.4" -dirs = "1.0" +toml = "0.7" +dirs = "5" +hyper-tls = "0.5.0" +base64 = "0.21.0" +itertools = "0.10.5" diff --git a/chef_api/src/api_client.rs b/chef_api/src/api_client.rs index 9b042fa..7bde347 100644 --- a/chef_api/src/api_client.rs +++ b/chef_api/src/api_client.rs @@ -2,14 +2,11 @@ use crate::credentials::Config; use hyper::client::HttpConnector; use hyper::Client as HyperClient; -use hyper_openssl::HttpsConnector; - -use tokio_core::reactor::Core; +use hyper_tls::HttpsConnector; use failure::Error; use serde::ser::*; use serde_json::Value; -use std::cell::RefCell; use std::rc::Rc; use crate::requests::*; @@ -21,24 +18,17 @@ pub struct ApiClient { pub config: Config, /// The Hyper HTTP Client. pub client: Rc>>, - /// The async core - pub core: Rc>, } impl ApiClient { /// Create a new ApiClient struct. It takes a `Config` type. Typically one would use /// `from_credentials` rather than calling this directly. pub fn new(config: Config) -> Result { - let core = Core::new()?; - let handle = core.handle(); - - let client = HyperClient::configure() - .connector(HttpsConnector::new(4, &handle)?) - .build(&handle); + let https = HttpsConnector::new(); + let client = HyperClient::builder().build(https); Ok(Self { config, - core: Rc::new(RefCell::new(core)), client: Rc::new(client), }) } diff --git a/chef_api/src/authentication/auth11.rs b/chef_api/src/authentication/auth11.rs index 53c7ad2..ddd1544 100644 --- a/chef_api/src/authentication/auth11.rs +++ b/chef_api/src/authentication/auth11.rs @@ -1,13 +1,13 @@ -use crate::authentication::BASE64_AUTH; -use crate::http_headers::*; use crate::utils::{expand_string, squeeze_path}; +use base64::{engine::general_purpose, Engine as _}; use chrono::*; use failure::Error; -use hyper::header::Headers; +use hyper::header::{HeaderMap, HeaderName, HeaderValue}; +use itertools::Itertools; use openssl::hash::{hash, MessageDigest}; use openssl::rsa::Padding; use openssl::rsa::Rsa; -use rustc_serialize::base64::ToBase64; +use std::convert::TryFrom; use std::fmt; pub struct Auth11 { @@ -59,20 +59,22 @@ impl Auth11 { fn hashed_path(&self) -> Result { debug!("Path is: {:?}", self.path); - let hash = hash(MessageDigest::sha1(), self.path.as_bytes())?.to_base64(BASE64_AUTH); + let hash = hash(MessageDigest::sha1(), self.path.as_bytes())?; + let hash = general_purpose::STANDARD.encode(hash); Ok(hash) } fn content_hash(&self) -> Result { let body = expand_string(&self.body); - let content = hash(MessageDigest::sha1(), body.as_bytes())?.to_base64(BASE64_AUTH); + let content = hash(MessageDigest::sha1(), body.as_bytes())?; + let content = general_purpose::STANDARD.encode(content); debug!("{:?}", content); Ok(content) } fn canonical_user_id(&self) -> Result { hash(MessageDigest::sha1(), self.userid.as_bytes()) - .and_then(|res| Ok(res.to_base64(BASE64_AUTH))) + .and_then(|res| Ok(general_purpose::STANDARD.encode(res))) .map_err(|res| res.into()) } @@ -99,22 +101,28 @@ impl Auth11 { let mut hash: Vec = vec![0; key.size() as usize]; key.private_encrypt(cr, &mut hash, Padding::PKCS1)?; - Ok(hash.to_base64(BASE64_AUTH)) + Ok(general_purpose::STANDARD.encode(hash)) } - pub fn build(self, headers: &mut Headers) -> Result<(), Error> { + pub fn build(self, headers: &mut HeaderMap) -> Result<(), Error> { let hsh = self.content_hash()?; - headers.set(OpsContentHash(hsh)); + headers.insert("X-Ops-Content-Hash", HeaderValue::from_str(&hsh)?); - headers.set(OpsSign(String::from("algorithm=sha1;version=1.1"))); - headers.set(OpsTimestamp(self.date.clone())); - headers.set(OpsUserId(self.userid.clone())); + headers.insert( + "X-Ops-Sign", + HeaderValue::from_str("algorithm=sha1;version=1.1")?, + ); + headers.insert("X-Ops-Timestamp", HeaderValue::from_str(&self.date)?); + headers.insert("X-Ops-Userid", HeaderValue::from_str(&self.userid)?); let enc = self.encrypted_request()?; let mut i = 1; - for h in enc.split('\n') { + for h in &enc.bytes().chunks(60) { let key = format!("X-Ops-Authorization-{}", i); - headers.set_raw(key, vec![h.as_bytes().to_vec()]); + headers.insert( + HeaderName::try_from(key)?, + HeaderValue::from_bytes(&h.collect::>())?, + ); i += 1; } Ok(()) @@ -192,13 +200,12 @@ mod tests { }; assert_eq!( &auth.encrypted_request().unwrap(), - "UfZD9dRz6rFu6LbP5Mo1oNHcWYxpNIcUfFCffJS1FQa0GtfU/vkt3/O5HuCM\n\ - 1wIFl/U0f5faH9EWpXWY5NwKR031Myxcabw4t4ZLO69CIh/3qx1XnjcZvt2w\n\ - c2R9bx/43IWA/r8w8Q6decuu0f6ZlNheJeJhaYPI8piX/aH+uHBH8zTACZu8\n\ - vMnl5MF3/OIlsZc8cemq6eKYstp8a8KYq9OmkB5IXIX6qVMJHA6fRvQEB/7j\n\ - 281Q7oI/O+lE8AmVyBbwruPb7Mp6s4839eYiOdjbDwFjYtbS3XgAjrHlaD7W\n\ + "UfZD9dRz6rFu6LbP5Mo1oNHcWYxpNIcUfFCffJS1FQa0GtfU/vkt3/O5HuCM\ + 1wIFl/U0f5faH9EWpXWY5NwKR031Myxcabw4t4ZLO69CIh/3qx1XnjcZvt2w\ + c2R9bx/43IWA/r8w8Q6decuu0f6ZlNheJeJhaYPI8piX/aH+uHBH8zTACZu8\ + vMnl5MF3/OIlsZc8cemq6eKYstp8a8KYq9OmkB5IXIX6qVMJHA6fRvQEB/7j\ + 281Q7oI/O+lE8AmVyBbwruPb7Mp6s4839eYiOdjbDwFjYtbS3XgAjrHlaD7W\ FDlbAG7H8Dmvo+wBxmtNkszhzbBnEYtuwQqT8nM/8A==" ) } - } diff --git a/chef_api/src/authentication/auth13.rs b/chef_api/src/authentication/auth13.rs index 18b33fd..2659e83 100644 --- a/chef_api/src/authentication/auth13.rs +++ b/chef_api/src/authentication/auth13.rs @@ -1,13 +1,13 @@ -use crate::authentication::BASE64_AUTH; -use crate::http_headers::*; use crate::utils::{expand_string, squeeze_path}; +use base64::{engine::general_purpose, Engine as _}; use chrono::*; use failure::Error; -use hyper::header::Headers; +use hyper::header::{HeaderMap, HeaderName, HeaderValue}; +use itertools::Itertools; use openssl::hash::{hash, MessageDigest}; use openssl::pkey::PKey; use openssl::sign::Signer; -use rustc_serialize::base64::ToBase64; +use std::convert::TryFrom; use std::fmt; pub struct Auth13 { @@ -59,7 +59,8 @@ impl Auth13 { fn content_hash(&self) -> Result { let body = expand_string(&self.body); debug!("Content body is: {:?}", body); - let content = hash(MessageDigest::sha256(), body.as_bytes())?.to_base64(BASE64_AUTH); + let content = hash(MessageDigest::sha256(), body.as_bytes())?; + let content = general_purpose::STANDARD.encode(content); debug!("Content hash is: {:?}", content); Ok(content) } @@ -89,23 +90,29 @@ impl Auth13 { let mut signer = Signer::new(MessageDigest::sha256(), &key)?; signer.update(cr).unwrap(); let result = signer.sign_to_vec()?; - let result = result.to_base64(BASE64_AUTH); + let result = general_purpose::STANDARD.encode(result); debug!("base64 encoded result is {:?}", result); Ok(result) } - pub fn build(self, headers: &mut Headers) -> Result<(), Error> { + pub fn build(self, headers: &mut HeaderMap) -> Result<(), Error> { let hsh = self.content_hash()?; - headers.set(OpsContentHash(hsh)); - headers.set(OpsSign(String::from("algorithm=sha256;version=1.3"))); - headers.set(OpsTimestamp(self.date.clone())); - headers.set(OpsUserId(self.userid.clone())); + + headers.insert("X-Ops-Content-Hash", HeaderValue::from_str(&hsh)?); + headers.insert( + "X-Ops-Sign", + HeaderValue::from_str("algorithm=sha256;version=1.3")?, + ); + headers.insert("X-Ops-Timestamp", HeaderValue::from_str(&self.date)?); + headers.insert("X-Ops-Userid", HeaderValue::from_str(&self.userid)?); let enc = self.signed_request()?; let mut i = 1; - for h in enc.split('\n') { + for h in &enc.bytes().chunks(60) { let key = format!("X-Ops-Authorization-{}", i); - headers.set_raw(key, vec![h.as_bytes().to_vec()]); + let value = h.collect::>(); + let value = HeaderValue::from_bytes(&value)?; + headers.insert(HeaderName::try_from(key)?, value); i += 1; } Ok(()) @@ -116,10 +123,10 @@ impl Auth13 { mod tests { use super::Auth13; + use base64::{engine::general_purpose, Engine as _}; use openssl::hash::MessageDigest; use openssl::pkey::PKey; use openssl::sign::Verifier; - use rustc_serialize::base64::FromBase64; use std::fs::File; use std::io::Read; @@ -172,7 +179,7 @@ mod tests { let sig = &auth.signed_request().unwrap(); let req = &auth.canonical_request().unwrap(); - let sig_raw = sig.clone().from_base64().unwrap(); + let sig_raw = general_purpose::STANDARD.decode(&sig).unwrap(); let mut key: Vec = vec![]; let mut fh = File::open(PRIVATE_KEY).unwrap(); fh.read_to_end(&mut key).unwrap(); @@ -184,13 +191,12 @@ mod tests { assert_eq!( sig, - "FZOmXAyOBAZQV/uw188iBljBJXOm+m8xQ/8KTGLkgGwZNcRFxk1m953XjE3W\n\ - VGy1dFT76KeaNWmPCNtDmprfH2na5UZFtfLIKrPv7xm80V+lzEzTd9WBwsfP\n\ - 42dZ9N+V9I5SVfcL/lWrrlpdybfceJC5jOcP5tzfJXWUITwb6Z3Erg3DU3Uh\n\ - H9h9E0qWlYGqmiNCVrBnpe6Si1gU/Jl+rXlRSNbLJ4GlArAPuL976iTYJTzE\n\ - MmbLUIm3JRYi00Yb01IUCCKdI90vUq1HHNtlTEu93YZfQaJwRxXlGkCNwIJe\n\ + "FZOmXAyOBAZQV/uw188iBljBJXOm+m8xQ/8KTGLkgGwZNcRFxk1m953XjE3W\ + VGy1dFT76KeaNWmPCNtDmprfH2na5UZFtfLIKrPv7xm80V+lzEzTd9WBwsfP\ + 42dZ9N+V9I5SVfcL/lWrrlpdybfceJC5jOcP5tzfJXWUITwb6Z3Erg3DU3Uh\ + H9h9E0qWlYGqmiNCVrBnpe6Si1gU/Jl+rXlRSNbLJ4GlArAPuL976iTYJTzE\ + MmbLUIm3JRYi00Yb01IUCCKdI90vUq1HHNtlTEu93YZfQaJwRxXlGkCNwIJe\ fy49QzaCIEu1XiOx5Jn+4GmkrZch/RrK9VzQWXgs+w==" ) } - } diff --git a/chef_api/src/authentication/mod.rs b/chef_api/src/authentication/mod.rs index 7619c00..e4a6ef9 100644 --- a/chef_api/src/authentication/mod.rs +++ b/chef_api/src/authentication/mod.rs @@ -1,11 +1,2 @@ -use rustc_serialize::base64::{CharacterSet, Config, Newline}; - pub mod auth11; pub mod auth13; - -pub static BASE64_AUTH: Config = Config { - char_set: CharacterSet::Standard, - newline: Newline::LF, - pad: true, - line_length: Some(60), -}; diff --git a/chef_api/src/errors.rs b/chef_api/src/errors.rs index f315c39..ad8045a 100644 --- a/chef_api/src/errors.rs +++ b/chef_api/src/errors.rs @@ -23,9 +23,9 @@ pub enum ChefError { display = "An error occurred attempting to parse the Chef Server URL: {}", _0 )] - UriError(#[cause] hyper::error::UriError), + UriError(#[cause] hyper::Error), #[fail(display = "An error occurred communicating to the Chef Server: {}", _0)] - HTTPError(#[cause] hyper::error::Error), + HTTPError(#[cause] hyper::Error), #[fail(display = "An error occurred when using the API client: {}", _0)] BorrowError(#[cause] std::cell::BorrowMutError), #[fail(display = "Failed to parse credentials file: {}", _0)] diff --git a/chef_api/src/http_headers.rs b/chef_api/src/http_headers.rs deleted file mode 100644 index 2d202d2..0000000 --- a/chef_api/src/http_headers.rs +++ /dev/null @@ -1,21 +0,0 @@ -header! { - (OpsSign, "X-Ops-Sign") => [String] -} -header! { - (OpsUserId, "X-Ops-Userid") => [String] -} -header! { - (OpsTimestamp, "X-Ops-Timestamp") => [String] -} -header! { - (OpsContentHash, "X-Ops-Content-Hash") => [String] -} -header! { - (OpsApiVersion, "X-Ops-Server-API-Version") => [u8] -} -header! { - (OpsApiInfo, "X-Ops-Server-API-Info") => [u8] -} -header! { - (ChefVersion, "X-Chef-Version") => [String] -} diff --git a/chef_api/src/lib.rs b/chef_api/src/lib.rs index 11bbc02..d66fe70 100644 --- a/chef_api/src/lib.rs +++ b/chef_api/src/lib.rs @@ -33,15 +33,13 @@ extern crate failure; extern crate chrono; extern crate openssl; -extern crate rustc_serialize; extern crate url; extern crate futures; -#[macro_use] extern crate hyper; extern crate hyper_openssl; -extern crate tokio_core; +extern crate tokio; #[macro_use] extern crate log; @@ -58,7 +56,6 @@ extern crate dirs; pub use crate::errors::*; pub mod authentication; pub mod errors; -mod http_headers; #[macro_use] mod macros; pub mod credentials; diff --git a/chef_api/src/macros.rs b/chef_api/src/macros.rs index 4bd2264..4794580 100644 --- a/chef_api/src/macros.rs +++ b/chef_api/src/macros.rs @@ -1,10 +1,10 @@ macro_rules! build { ($name:ident, $type:ident) => { - #[doc="Generate a new $type request."] + #[doc = "Generate a new $type request."] pub fn $name(&self) -> $type { self.into() } - } + }; } macro_rules! import { @@ -14,24 +14,20 @@ macro_rules! import { use $crate::authentication::auth11::Auth11; use $crate::authentication::auth13::Auth13; use $crate::credentials::Config; - use $crate::http_headers::*; use $crate::utils::add_path_element; use serde::Serialize; use serde_json; - use std::cell::RefCell; use std::rc::Rc; - use futures::{Future, Stream}; - use tokio_core::reactor::Core; + use tokio::runtime::Runtime; use hyper::client::HttpConnector; - use hyper::header::{qitem, Accept, ContentLength, ContentType}; - use hyper::mime::APPLICATION_JSON; + use hyper::header::{self, HeaderValue}; use hyper::Client as HyperClient; use hyper::{Method, Request}; - use hyper_openssl::HttpsConnector; + use hyper_tls::HttpsConnector; }; } @@ -82,7 +78,7 @@ macro_rules! acls { self.path = add_path_element(self.path.clone(), permission); self } - } + }; } macro_rules! request_type { @@ -90,10 +86,10 @@ macro_rules! request_type { #[derive(Debug, Clone)] pub struct $n<'c> { pub(crate) client: &'c Rc>>, - pub(crate) core: &'c Rc>, pub(crate) config: &'c Config, pub(crate) path: String, pub(crate) api_version: String, + pub(crate) q: Option, } }; } @@ -108,9 +104,9 @@ macro_rules! requests { Self { config: &api.config, client: &api.client, - core: &api.core, path, api_version: String::from("1"), + q: None, } } } @@ -126,9 +122,9 @@ macro_rules! requests { Self { config: &api.config, client: &api.client, - core: &api.core, path, api_version: String::from("1"), + q: None, } } } @@ -145,9 +141,9 @@ macro_rules! requests { Self { config: &api.config, client: &api.client, - core: &api.core, path, api_version: String::from("1"), + q: None, } } } @@ -178,17 +174,21 @@ macro_rules! execute { let path = self.path.clone(); let api_version = self.api_version.clone(); - let url = format!("{}{}", &self.config.url_base()?, path).parse()?; + let mut url = url::Url::parse(&format!("{}{}", &self.config.url_base()?, path))?; //.parse()?; + if self.q.is_some() { + url.query_pairs_mut() + .append_pair("q", self.q.as_ref().unwrap()); + } let mth = match method { - "put" => Method::Put, - "post" => Method::Post, - "delete" => Method::Delete, - "head" => Method::Head, - _ => Method::Get, + "put" => Method::PUT, + "post" => Method::POST, + "delete" => Method::DELETE, + "head" => Method::HEAD, + _ => Method::GET, }; - let mut request = Request::new(mth, url); + let mut req_builder = Request::builder().method(mth).uri(url.as_str()); let body = match body { Some(b) => serde_json::to_string(&b)?, @@ -204,7 +204,7 @@ macro_rules! execute { &api_version, Some(body.clone().into()), ) - .build(request.headers_mut())?, + .build(req_builder.headers_mut().unwrap())?, _ => Auth13::new( &path, &key, @@ -213,48 +213,56 @@ macro_rules! execute { &api_version, Some(body.clone().into()), ) - .build(request.headers_mut())?, + .build(req_builder.headers_mut().unwrap())?, }; - let json = APPLICATION_JSON; - request.headers_mut().set(Accept(vec![qitem(json.clone())])); - request.headers_mut().set(ContentType::json()); - request - .headers_mut() - .set(ContentLength(body.clone().len() as u64)); - request.headers_mut().set(OpsApiInfo(1)); - request.headers_mut().set(OpsApiVersion(1)); - request - .headers_mut() - .set(ChefVersion(String::from("13.3.34"))); + req_builder.headers_mut().map(|h| { + h.insert( + header::ACCEPT, + HeaderValue::from_str("application/json").unwrap(), + ); + h.insert( + header::CONTENT_TYPE, + HeaderValue::from_str("application/json").unwrap(), + ); + h.insert( + header::CONTENT_LENGTH, + HeaderValue::from(body.clone().len() as u64), + ); + h.insert("X-Ops-Server-API-Info", HeaderValue::from(1 as u64)); + h.insert("X-Ops-Server-API-Version", HeaderValue::from(1 as u64)); + h.insert("X-Chef-Version", HeaderValue::from_str("13.3.34").unwrap()); + }); - request.set_body(body); + let request = req_builder.body(body.into())?; let client = self.client; - let resp = client - .request(request) - .map_err(ChefError::HTTPError) - .and_then(|res| { - debug!("Status is {:?}", res.status()); - - let status = res.status(); - res.body() - .concat2() - .map_err(ChefError::HTTPError) - .and_then(move |body| { - let body: Value = - serde_json::from_slice(&body).map_err(ChefError::JsonError)?; - - if status.is_success() { - Ok(body) - } else { - Err(ChefError::ChefServerResponseError(status.as_u16())) - } - }) - }); - - let mut core = self.core.try_borrow_mut()?; - core.run(resp).map_err(|e| e.into()) + let resp = async { + let res = client + .request(request) + .await + .map_err(ChefError::HTTPError)?; + debug!("Status is {:?}", res.status()); + + let status = res.status(); + let body = hyper::body::to_bytes(res.into_body()) + .await + .map_err(ChefError::HTTPError)?; + + trace!("{}", String::from_utf8_lossy(&body)); + + let body: Value = + serde_json::from_slice(&body).map_err(ChefError::JsonError)?; + + if status.is_success() { + Ok(body) + } else { + Err(ChefError::ChefServerResponseError(status.as_u16())) + } + }; + + let rt = Runtime::new()?; + rt.block_on(resp).map_err(|e| e.into()) } } }; diff --git a/chef_api/src/requests/search.rs b/chef_api/src/requests/search.rs index ea04285..cb11974 100644 --- a/chef_api/src/requests/search.rs +++ b/chef_api/src/requests/search.rs @@ -3,4 +3,9 @@ requests!(SearchQuery, search); impl<'c> SearchQuery<'c> { path!(search_index); + + pub fn q(&mut self, query: &str) -> &mut Self { + self.q = Some(query.to_owned()); + self + } } diff --git a/examples/simple.rs b/examples/simple.rs index ac1621f..2005cb4 100644 --- a/examples/simple.rs +++ b/examples/simple.rs @@ -11,7 +11,7 @@ use chef::models::*; use chef_api::api_client::*; pub fn main() { - env_logger::init().unwrap(); + env_logger::init(); let client = ApiClient::from_credentials(None).unwrap(); let nodes = client.nodes().get(); diff --git a/examples/simple2.rs b/examples/simple2.rs new file mode 100644 index 0000000..83d58cc --- /dev/null +++ b/examples/simple2.rs @@ -0,0 +1,122 @@ +extern crate chef; +extern crate chef_api; + +extern crate env_logger; +use log::{self, error}; + +use serde::Deserialize; +extern crate serde_json; + +use std::collections::HashMap; + +use chef::models::*; +use chef_api::api_client::*; +use log::info; + +pub fn main() { + env_logger::init(); + let client = ApiClient::from_credentials(None).unwrap(); + + let mut fields: HashMap<&str, Vec<&str>> = HashMap::new(); + fields.insert("name", vec!["name"]); + fields.insert("ipaddress", vec!["ipaddress"]); + fields.insert("ip6address", vec!["ip6address"]); + fields.insert("chef_environment", vec!["chef_environment"]); + fields.insert("network", vec!["network"]); + + println!("Starting search"); + let n = client + .search() + .search_index("node") + .q("role:rb_vault_server") + .post(&fields) + // .get() + .unwrap(); + println!("Done searching"); + + // let nr: NodeResult = n.into(); + let nr: PartialResultResult = n.into(); + for n in nr { + info!("{:?}", n); + /* println!( + "{}: {}", + n.data.get("name").unwrap(), + n.data.get("ipaddress").unwrap() + ); */ + let network: Network = + serde_json::from_value(n.data.get("network").unwrap().clone()).unwrap(); + println!("{:?}", network); + println!("{:?}", get_addresses(&network, Some("inet6"))); + } +} + +fn get_addresses(network: &Network, family: Option<&str>) -> Vec { + let mut result = Vec::new(); + for (_interface_name, interface) in network.interfaces.iter() { + for (address, addr_info) in interface.addresses.iter() { + if (family.is_some() && addr_info.family == family.unwrap()) + || (family.is_none() && addr_info.family.starts_with("inet")) + { + match addr_info.scope.as_str() { + "Global" => result.push(address.clone()), + "Node" => (), // Loopback etc + "Link" => (), // non-routable + _ => error!("Address scope '{}' unknown", addr_info.scope), + }; + } + } + } + + result +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct Network { + pub interfaces: HashMap, + #[serde(default)] + pub default_gateway: String, + #[serde(default)] + pub default_interface: String, +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct Interface { + #[serde(default)] + pub addresses: HashMap, + #[serde(default)] + pub state: String, + #[serde(default)] + pub routes: Vec, +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct AddressInfo { + #[serde(default)] + pub family: String, + #[serde(default)] + pub prefixlen: String, + #[serde(default)] + pub netmask: String, + #[serde(default)] + pub scope: String, + #[serde(default)] + pub tags: Vec, +} + +#[derive(Deserialize, Debug, Clone, Default)] +pub struct Route { + #[serde(default)] + pub destination: String, + #[serde(default)] + pub family: String, + #[serde(default)] + pub via: String, + #[serde(default)] + pub metric: String, + #[serde(default)] + pub proto: String, + #[serde(default)] + pub scope: String, + #[serde(default)] + pub src: String, +} diff --git a/src/models/client.rs b/src/models/client.rs index 4cc0ab4..1bba059 100644 --- a/src/models/client.rs +++ b/src/models/client.rs @@ -16,3 +16,4 @@ pub struct Client { model_impl!(Client); model_list!(ClientList); +model_result!(Client, ClientResult); diff --git a/src/models/mod.rs b/src/models/mod.rs index cb5878f..7b1c5e1 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -15,19 +15,13 @@ macro_rules! model_list { ($id:ident) => { #[derive(Debug)] pub struct $id { - count: usize, items: Vec, } impl From for $id { fn from(list: Value) -> Self { decode_list(&list) - .and_then(|list| { - Ok(Self { - items: list, - count: 0, - }) - }) + .and_then(|list| Ok(Self { items: list })) .unwrap() } } @@ -50,6 +44,48 @@ macro_rules! model_list { }; } +macro_rules! model_result { + ($model:ident, $id:ident) => { + #[derive(Debug)] + pub struct $id { + items: Vec<$model>, + } + + impl From for $id { + fn from(list: Value) -> Self { + assert!(list.is_object()); + let list = list.get("rows").unwrap(); + + assert!(list.is_array()); + let mut output: Vec<$model> = Vec::new(); + + for json_node in list.as_array().unwrap().to_owned() { + log::trace!("{:?}", &json_node); + output.push($model::try_from(json_node).unwrap()); + } + + $id { items: output } + } + } + + impl Iterator for $id { + type Item = $model; + + fn count(self) -> usize { + self.items.len() + } + + fn next(&mut self) -> Option { + if self.items.len() >= 1 { + Some(self.items.remove(0)) + } else { + None + } + } + } + }; +} + macro_rules! model_impl { ($id:ident) => { impl Read for $id { @@ -104,3 +140,5 @@ pub mod cookbook; pub use self::cookbook::*; pub mod client; pub use self::client::*; +pub mod partial_result; +pub use self::partial_result::*; diff --git a/src/models/node.rs b/src/models/node.rs index 130ecdf..9f8eaf5 100644 --- a/src/models/node.rs +++ b/src/models/node.rs @@ -7,16 +7,24 @@ chef_json_type!(NodeChefType, "node"); #[serde(default)] pub struct Node { pub name: Option, + #[serde(default)] chef_type: NodeChefType, + #[serde(default)] json_class: NodeJsonClass, + #[serde(default)] pub chef_environment: String, + #[serde(default)] pub run_list: Vec, + #[serde(default)] pub normal: HashMap, + #[serde(default)] pub automatic: HashMap, + #[serde(default)] pub default: HashMap, - #[serde(rename = "override")] + #[serde(default, rename = "override")] pub overrides: HashMap, } model_impl!(Node); model_list!(NodeList); +model_result!(Node, NodeResult); diff --git a/src/models/partial_result.rs b/src/models/partial_result.rs new file mode 100644 index 0000000..5320ae7 --- /dev/null +++ b/src/models/partial_result.rs @@ -0,0 +1,12 @@ +model_use!(); + +#[derive(Debug, Clone, Serialize, Deserialize, Default)] +#[serde(default)] +pub struct PartialResult { + pub url: String, + pub data: Value, +} + +model_impl!(PartialResult); +model_list!(PartialResultList); +model_result!(PartialResult, PartialResultResult); diff --git a/src/models/role.rs b/src/models/role.rs index ba98643..38a9c6a 100644 --- a/src/models/role.rs +++ b/src/models/role.rs @@ -24,3 +24,4 @@ pub struct Role { model_impl!(Role); model_list!(RoleList); +model_result!(Role, RoleResult);