From 36a3b3c15500f96085a88c4b32ca684374fde856 Mon Sep 17 00:00:00 2001 From: Krishnan Parthasarathi Date: Tue, 18 Jun 2019 16:33:06 -0700 Subject: [PATCH] Add list_buckets api (#1) --- src/main.rs | 15 +++++++++++++++ src/minio.rs | 23 +++++++++++++++++++++++ src/minio/types.rs | 22 ++++++++++++++++++++++ src/minio/xml.rs | 35 ++++++++++++++++++++++++++++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index ec2b3e0..92f3422 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod minio; use futures::{future::Future, stream::Stream}; use hyper::rt; +use minio::BucketInfo; fn get_local_default_server() -> minio::Client { match minio::Client::new("http://localhost:9000") { @@ -48,8 +49,22 @@ fn main() { .map(|c| println!("get obj res: {:?}", c)) .map_err(|c| println!("err res: {:?}", c)); + let list_buckets_req = c + .list_buckets() + .map(|buckets| { + println!( + "{:?}", + buckets + .iter() + .map(|bucket: &BucketInfo| bucket.name.clone()) + .collect::>() + ) + }) + .map_err(|err| println!("{:?}", err)); + del_req .join5(make_bucket_req, region_req, buc_exists_req, download_req) .map(|_| ()) + .then(|_| list_buckets_req) })); } diff --git a/src/minio.rs b/src/minio.rs index 3549a68..ec0c485 100644 --- a/src/minio.rs +++ b/src/minio.rs @@ -20,6 +20,8 @@ use time::Tm; use types::{Err, GetObjectResp, Region}; +pub use types::BucketInfo; + #[derive(Debug, Clone)] pub struct Credentials { access_key: String, @@ -289,6 +291,27 @@ impl Client { self.signed_req_future(s3_req, xml_body_res) .and_then(|_| future::ok(())) } + + pub fn list_buckets(&self) -> impl Future, Error = Err> { + let s3_req = S3Req { + method: Method::GET, + bucket: None, + object: None, + query: HashMap::new(), + headers: HeaderMap::new(), + body: Body::empty(), + ts: time::now_utc(), + }; + self.signed_req_future(s3_req, Ok(Body::empty())) + .and_then(|resp| { + // Read the whole body for list buckets response. + resp.into_body() + .concat2() + .map_err(|err| Err::HyperErr(err)) + .and_then(move |chunk| b2s(chunk.into_bytes())) + .and_then(|s| xml::parse_bucket_list(s)) + }) + } } fn run_req_future( diff --git a/src/minio/types.rs b/src/minio/types.rs index a6dd06b..1567d63 100644 --- a/src/minio/types.rs +++ b/src/minio/types.rs @@ -7,6 +7,7 @@ use hyper::header::{ use hyper::{body::Body, Response}; use roxmltree; use std::string; +use time::{strptime, Tm}; #[derive(Clone)] pub struct Region(String); @@ -29,11 +30,13 @@ impl Region { pub enum Err { InvalidUrl(String), InvalidEnv(String), + InvalidTmFmt(String), HttpErr(http::Error), HyperErr(hyper::Error), FailStatusCodeErr(hyper::StatusCode, Bytes), Utf8DecodingErr(string::FromUtf8Error), XmlParseErr(roxmltree::Error), + InvalidXmlResponseErr(String), MissingRequiredParams, RawSvcErr(hyper::StatusCode, Response), XmlWriteErr(String), @@ -97,3 +100,22 @@ fn extract_user_meta(h: &HeaderMap) -> Vec<(String, String)> { .map(|(k, v)| (k.to_string(), v.unwrap_or("").to_string())) .collect() } + +#[derive(Debug)] +pub struct BucketInfo { + pub name: String, + pub created_time: Tm, +} + +impl BucketInfo { + pub fn new(name: &str, time_str: &str) -> Result { + strptime(time_str, "%Y-%m-%dT%H:%M:%S.%Z") + .and_then(|ctime| { + Ok(BucketInfo { + name: name.to_string(), + created_time: ctime, + }) + }) + .or_else(|err| Err(format!("{:?}", err))) + } +} diff --git a/src/minio/xml.rs b/src/minio/xml.rs index 3852504..31130d6 100644 --- a/src/minio/xml.rs +++ b/src/minio/xml.rs @@ -1,4 +1,4 @@ -use crate::minio::types::{Err, Region}; +use crate::minio::types::{BucketInfo, Err, Region}; use crate::minio::woxml; use hyper::body::Body; use roxmltree; @@ -18,6 +18,39 @@ pub fn parse_bucket_location(s: String) -> Result { } } +pub fn parse_bucket_list(s: String) -> Result, Err> { + let res = roxmltree::Document::parse(&s); + match res { + Ok(doc) => { + let mut bucket_infos: Vec = Vec::new(); + let bucket_nodes = doc + .root_element() + .descendants() + .filter(|node| node.has_tag_name("Bucket")); + for bucket in bucket_nodes { + let bucket_names = bucket.children().filter(|node| node.has_tag_name("Name")); + let bucket_ctimes = bucket + .children() + .filter(|node| node.has_tag_name("CreationDate")); + for (name_node, ctime_node) in bucket_names.zip(bucket_ctimes) { + let name = name_node.text().ok_or(Err::InvalidXmlResponseErr( + "Missing name in list buckets XML response ".to_string(), + ))?; + let ctime = ctime_node.text().ok_or(Err::InvalidXmlResponseErr( + "Missing creation date in list buckets XML response".to_string(), + ))?; + match BucketInfo::new(name, ctime) { + Ok(bucket_info) => bucket_infos.push(bucket_info), + Err(err) => return Err(Err::InvalidTmFmt(format!("{:?}", err))), + } + } + } + Ok(bucket_infos) + } + Err(err) => Err(Err::XmlParseErr(err)), + } +} + pub fn get_mk_bucket_body() -> Result { let lc_node = woxml::XmlNode::new("LocationConstraint").text("us-east-1"); let mk_bucket_xml = woxml::XmlNode::new("CreateBucketConfiguration")