From c0d22c0be5e1707b2ba551bb8080e6f9ad65ad86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 21 Mar 2023 17:22:44 +0000 Subject: [PATCH 01/17] First try at implementing search result macro --- chef_api/src/macros.rs | 18 +++++++++---- chef_api/src/requests/search.rs | 5 ++++ src/models/mod.rs | 45 +++++++++++++++++++++++++++++++++ src/models/node.rs | 1 + 4 files changed, 64 insertions(+), 5 deletions(-) diff --git a/chef_api/src/macros.rs b/chef_api/src/macros.rs index 4bd2264..fe0b87f 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 { @@ -82,7 +82,7 @@ macro_rules! acls { self.path = add_path_element(self.path.clone(), permission); self } - } + }; } macro_rules! request_type { @@ -94,6 +94,7 @@ macro_rules! request_type { pub(crate) config: &'c Config, pub(crate) path: String, pub(crate) api_version: String, + pub(crate) q: Option, } }; } @@ -111,6 +112,7 @@ macro_rules! requests { core: &api.core, path, api_version: String::from("1"), + q: None, } } } @@ -129,6 +131,7 @@ macro_rules! requests { core: &api.core, path, api_version: String::from("1"), + q: None, } } } @@ -148,6 +151,7 @@ macro_rules! requests { core: &api.core, path, api_version: String::from("1"), + q: None, } } } @@ -178,7 +182,11 @@ 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, @@ -188,7 +196,7 @@ macro_rules! execute { _ => Method::Get, }; - let mut request = Request::new(mth, url); + let mut request = Request::new(mth, url.as_str().parse()?); let body = match body { Some(b) => serde_json::to_string(&b)?, 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/src/models/mod.rs b/src/models/mod.rs index cb5878f..e7bc143 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -50,6 +50,51 @@ macro_rules! model_list { }; } +macro_rules! model_result { + ($model:ident, $id:ident) => { + #[derive(Debug)] + pub struct $id { + count: usize, + 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() { + output.push($model::try_from(json_node).unwrap()); + } + + $id { + count: output.len(), + 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 { diff --git a/src/models/node.rs b/src/models/node.rs index 130ecdf..90ab6e1 100644 --- a/src/models/node.rs +++ b/src/models/node.rs @@ -20,3 +20,4 @@ pub struct Node { model_impl!(Node); model_list!(NodeList); +model_result!(Node, NodeResult); From fef676b1374684e8aaff7ba87d54ad9fe80aa307 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 21 Mar 2023 17:24:07 +0000 Subject: [PATCH 02/17] Added example for search --- examples/simple2.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 examples/simple2.rs diff --git a/examples/simple2.rs b/examples/simple2.rs new file mode 100644 index 0000000..02c5494 --- /dev/null +++ b/examples/simple2.rs @@ -0,0 +1,31 @@ +extern crate chef; +extern crate chef_api; + +extern crate env_logger; +#[macro_use] +extern crate log; + +extern crate serde_json; + +use chef::models::*; +use chef_api::api_client::*; + +pub fn main() { + env_logger::init().unwrap(); + let client = ApiClient::from_credentials(None).unwrap(); + + println!("Starting search"); + let n = client + .search() + .search_index("node") + .q("role:rb_vault_server") + .get() + .unwrap(); + println!("Done searching"); + + let nr: NodeResult = n.into(); + for n in nr { + println!("{}", n.name.clone().unwrap()); + // info!("{:?}", n); + } +} From daa86617bc19abb02f76d28742d9c650e6acc117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 21 Mar 2023 17:25:45 +0000 Subject: [PATCH 03/17] Removed count member from models since it is not used --- src/models/mod.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/models/mod.rs b/src/models/mod.rs index cb5878f..b9d402a 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() } } From 8f3fb5ec81dbb02bec2d9043744ceebf788ca964 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 21 Mar 2023 17:27:20 +0000 Subject: [PATCH 04/17] Removed count member from search models since it is not used --- src/models/mod.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/models/mod.rs b/src/models/mod.rs index d8d23ce..95cab6d 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -48,7 +48,6 @@ macro_rules! model_result { ($model:ident, $id:ident) => { #[derive(Debug)] pub struct $id { - count: usize, items: Vec<$model>, } @@ -64,10 +63,7 @@ macro_rules! model_result { output.push($model::try_from(json_node).unwrap()); } - $id { - count: output.len(), - items: output, - } + $id { items: output } } } From 728943b9d7a657ab0f83a9e3d6e3c6af17c6d332 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 21 Mar 2023 17:36:49 +0000 Subject: [PATCH 05/17] Added search macro to client and role models --- src/models/client.rs | 1 + src/models/role.rs | 1 + 2 files changed, 2 insertions(+) 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/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); From 27ab62155f37950e46c3a550db7cd89e39f00a56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 14:08:17 +0000 Subject: [PATCH 06/17] Updated log and env_logger dependencies --- Cargo.toml | 4 ++-- examples/simple.rs | 2 +- examples/simple2.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e3993a3..e6e12f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,11 +11,11 @@ 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" 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 index 02c5494..38bc37f 100644 --- a/examples/simple2.rs +++ b/examples/simple2.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(); println!("Starting search"); From c5b5a0f91a2a94fbc6fadfc84f2b9f7c1f04be34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 14:10:04 +0000 Subject: [PATCH 07/17] Updated chef_api dirs dependency --- chef_api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index 5ec24f3..28c13b1 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -29,4 +29,4 @@ serde_json = "1.0" failure = "0.1" toml = "0.4" -dirs = "1.0" +dirs = "5" From 6d1dd05a5cf41c26a2aeec0df23b400e97402f81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 14:11:47 +0000 Subject: [PATCH 08/17] Updated chef_api url dependency --- chef_api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index 28c13b1..9b0711a 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -18,7 +18,7 @@ tokio-core = "0.1" hyper = "0.11" hyper-openssl = "0.5" -url = "1.6" +url = "2" chrono = "0.4" openssl = "0.10" env_logger = "0.4" From 9f1645e08eaf67543b6b0e08249e86dbae168d39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 14:13:16 +0000 Subject: [PATCH 09/17] Updated log and env_logger dependencies --- chef_api/Cargo.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index 9b0711a..55d639a 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -10,7 +10,7 @@ edition = "2018" [dependencies] time = "0.1" -log = "0.3" +log = "0.4" rustc-serialize = "0.3" futures = "0.1" @@ -21,7 +21,7 @@ hyper-openssl = "0.5" 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" From 025a086409fe736c1b7afb493f64bb908e2907e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 14:14:27 +0000 Subject: [PATCH 10/17] Updated chef_api toml dependency --- chef_api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index 55d639a..bc7eb68 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -28,5 +28,5 @@ serde_json = "1.0" failure = "0.1" -toml = "0.4" +toml = "0.7" dirs = "5" From 6c321029132c345d3131ab8e6d9c7ffa8d459c30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 14:14:57 +0000 Subject: [PATCH 11/17] Updated chef_api time dependency --- chef_api/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index bc7eb68..5d8aec9 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -9,7 +9,7 @@ license = "Apache-2.0" edition = "2018" [dependencies] -time = "0.1" +time = "0.3" log = "0.4" rustc-serialize = "0.3" From 5d724efecbd671e88650d045e0887e4e55d6d03c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Wed, 22 Mar 2023 17:34:18 +0000 Subject: [PATCH 12/17] Updated all dependencies --- chef_api/Cargo.toml | 12 +-- chef_api/src/api_client.rs | 16 +--- chef_api/src/authentication/auth11.rs | 49 +++++++----- chef_api/src/authentication/auth13.rs | 48 +++++++----- chef_api/src/authentication/mod.rs | 9 --- chef_api/src/errors.rs | 4 +- chef_api/src/http_headers.rs | 21 ----- chef_api/src/lib.rs | 5 +- chef_api/src/macros.rs | 108 +++++++++++++------------- examples/simple2.rs | 1 - 10 files changed, 121 insertions(+), 152 deletions(-) delete mode 100644 chef_api/src/http_headers.rs diff --git a/chef_api/Cargo.toml b/chef_api/Cargo.toml index 5d8aec9..fc2e76b 100644 --- a/chef_api/Cargo.toml +++ b/chef_api/Cargo.toml @@ -11,12 +11,11 @@ edition = "2018" [dependencies] time = "0.3" log = "0.4" -rustc-serialize = "0.3" -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 = "2" chrono = "0.4" @@ -30,3 +29,6 @@ failure = "0.1" 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 fe0b87f..e169b74 100644 --- a/chef_api/src/macros.rs +++ b/chef_api/src/macros.rs @@ -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; }; } @@ -90,7 +86,6 @@ 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, @@ -109,7 +104,6 @@ macro_rules! requests { Self { config: &api.config, client: &api.client, - core: &api.core, path, api_version: String::from("1"), q: None, @@ -128,7 +122,6 @@ macro_rules! requests { Self { config: &api.config, client: &api.client, - core: &api.core, path, api_version: String::from("1"), q: None, @@ -148,7 +141,6 @@ macro_rules! requests { Self { config: &api.config, client: &api.client, - core: &api.core, path, api_version: String::from("1"), q: None, @@ -189,14 +181,14 @@ macro_rules! execute { } 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.as_str().parse()?); + let mut req_builder = Request::builder().method(mth).uri(url.as_str()); let body = match body { Some(b) => serde_json::to_string(&b)?, @@ -212,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, @@ -221,48 +213,54 @@ 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)?; + + 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/examples/simple2.rs b/examples/simple2.rs index 38bc37f..0d6ef89 100644 --- a/examples/simple2.rs +++ b/examples/simple2.rs @@ -2,7 +2,6 @@ extern crate chef; extern crate chef_api; extern crate env_logger; -#[macro_use] extern crate log; extern crate serde_json; From 5b47fb91677a0f2119ae1df6ae91e9fe567bb7bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 11 Jun 2024 15:58:52 +0000 Subject: [PATCH 13/17] Additional example --- Cargo.toml | 3 ++ examples/simple2.rs | 102 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e6e12f6..4134ade 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,9 @@ log = "0.4" chrono = "0.4" failure = "0.1" +[dev-dependencies] +serde = { version = "1.0", features = ["derive"]} + [features] default = [] dev = ["clippy"] diff --git a/examples/simple2.rs b/examples/simple2.rs index 0d6ef89..83d58cc 100644 --- a/examples/simple2.rs +++ b/examples/simple2.rs @@ -2,29 +2,121 @@ extern crate chef; extern crate chef_api; extern crate env_logger; -extern crate log; +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") - .get() + .post(&fields) + // .get() .unwrap(); println!("Done searching"); - let nr: NodeResult = n.into(); + // let nr: NodeResult = n.into(); + let nr: PartialResultResult = n.into(); for n in nr { - println!("{}", n.name.clone().unwrap()); - // info!("{:?}", n); + 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, +} From 2cc78355c7adf3d2ed08fc341871eab52d1091e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 11 Jun 2024 16:00:02 +0000 Subject: [PATCH 14/17] Add body to trace --- chef_api/src/macros.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/chef_api/src/macros.rs b/chef_api/src/macros.rs index e169b74..4794580 100644 --- a/chef_api/src/macros.rs +++ b/chef_api/src/macros.rs @@ -249,6 +249,8 @@ macro_rules! execute { .await .map_err(ChefError::HTTPError)?; + trace!("{}", String::from_utf8_lossy(&body)); + let body: Value = serde_json::from_slice(&body).map_err(ChefError::JsonError)?; From 61f9a06d88aa17197382f88e1e1d2bb712a27548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 11 Jun 2024 16:00:46 +0000 Subject: [PATCH 15/17] Add default attribute to node --- src/models/mod.rs | 3 +++ src/models/node.rs | 9 ++++++++- src/models/partial_result.rs | 12 ++++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 src/models/partial_result.rs diff --git a/src/models/mod.rs b/src/models/mod.rs index 95cab6d..7b1c5e1 100644 --- a/src/models/mod.rs +++ b/src/models/mod.rs @@ -60,6 +60,7 @@ macro_rules! model_result { 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()); } @@ -139,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 90ab6e1..9f8eaf5 100644 --- a/src/models/node.rs +++ b/src/models/node.rs @@ -7,14 +7,21 @@ 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, } 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); From 9aa75c81776e7a47a703a73a749f8ee6342b3315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 11 Jun 2024 16:02:48 +0000 Subject: [PATCH 16/17] Don't publish --- Cargo.toml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4134ade..bab2c4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,3 +28,6 @@ dev = ["clippy"] [workspace] members = [ "chef_api" ] + +[package.metadata.release] +publish = false From b5c6ec1c614b3b07d75d7ce380584a88e5515b21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=9E=C3=B3rhallur=20Sverrisson?= Date: Tue, 11 Jun 2024 16:03:23 +0000 Subject: [PATCH 17/17] chore: Release --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bab2c4a..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"