From ca8615746854c20db6b5181eb9662dc17239eeae Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Wed, 24 Oct 2018 23:07:27 +0200 Subject: [PATCH 1/4] API: substitute `Result` with `Future..` Before this commit all methods return a `Result` chaining them requires to `unwrap()` or slightly milder `unwrap_or_else()` for *every* additional chain element, which adds significant boilerplate for the consumer. This commit aims to aid with this by removing the outer `Result` and return `Future..` for all async public methods. --- src/lib.rs | 2 +- src/v2/auth.rs | 59 +++++++++++++++++++++++------------- src/v2/blobs.rs | 28 +++++++++++++---- src/v2/catalog.rs | 23 ++++++++++---- src/v2/manifest/mod.rs | 22 ++++++++------ src/v2/mod.rs | 18 +++++++---- src/v2/tags.rs | 6 ++-- tests/mock/api_version.rs | 12 ++++---- tests/mock/base_client.rs | 8 ++--- tests/mock/blobs_download.rs | 4 +-- tests/mock/catalog.rs | 4 +-- tests/mock/tags.rs | 8 ++--- tests/net/docker_io/mod.rs | 4 +-- tests/net/gcr_io/mod.rs | 10 +++--- tests/net/quay_io/mod.rs | 10 +++--- 15 files changed, 134 insertions(+), 84 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 3dec8fd5..1c10295d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,7 +21,7 @@ //! .insecure_registry(false) //! .registry(host) //! .build()?; -//! let check = dclient.is_v2_supported()?; +//! let check = dclient.is_v2_supported(); //! match tcore.run(check)? { //! false => println!("{} does NOT support v2", host), //! true => println!("{} supports v2", host), diff --git a/src/v2/auth.rs b/src/v2/auth.rs index 82af8113..ab88297d 100644 --- a/src/v2/auth.rs +++ b/src/v2/auth.rs @@ -23,10 +23,19 @@ impl TokenAuth { type FutureString = Box>; impl Client { - fn get_token_provider(&self) -> Result { - let url = try!(hyper::Uri::from_str( - (self.base_url.clone() + "/v2/").as_str() - )); + fn get_token_provider(&self) -> FutureString { + let url = { + let ep = format!("{}/v2/", self.base_url); + match hyper::Uri::from_str(ep.as_str()) { + Ok(url) => url, + Err(e) => { + return Box::new(futures::future::err::<_, _>(Error::from(format!( + "failed to parse url from string: {}", + e + )))) + } + } + }; let req = self.new_request(hyper::Method::GET, url); let freq = self.hclient.request(req); let www_auth = freq @@ -57,7 +66,7 @@ impl Client { } Ok(auth_ep) }); - Ok(Box::new(www_auth)) + Box::new(www_auth) } /// Set the token to be used for further registry requests. @@ -71,14 +80,14 @@ impl Client { /// Perform registry authentication and return an authenticated token. /// /// On success, the returned token will be valid for all requested scopes. - pub fn login(&self, scopes: &[&str]) -> Result { + pub fn login(&self, scopes: &[&str]) -> FutureTokenAuth { let subclient = self.hclient.clone(); let creds = self.credentials.clone(); let scope = scopes .iter() .fold("".to_string(), |acc, &s| acc + "&scope=" + s); let auth = self - .get_token_provider()? + .get_token_provider() .and_then(move |token_ep| { let auth_ep = token_ep + scope.as_str(); trace!("Token endpoint: {}", auth_ep); @@ -90,10 +99,14 @@ impl Client { if let Some(c) = creds { let plain = format!("{}:{}", c.0, c.1); let basic = format!("Basic {}", base64::encode(&plain)); - auth_req.headers_mut().append( - header::AUTHORIZATION, - header::HeaderValue::from_str(&basic).unwrap(), - ); + if let Ok(basic_header) = header::HeaderValue::from_str(&basic) { + auth_req + .headers_mut() + .append(header::AUTHORIZATION, basic_header); + } else { + // TODO: return an error. seems difficult to match the error type for the whole closure + error!("could not parse HeaderValue from '{}'", basic); + }; }; subclient.request(auth_req).map_err(|e| e.into()) }).and_then(|r| { @@ -113,21 +126,25 @@ impl Client { }).inspect(|_| { trace!("Got token"); }); - Ok(Box::new(auth)) + Box::new(auth) } /// Check whether the client is authenticated with the registry. - pub fn is_auth(&self, token: Option<&str>) -> Result { - let url = try!(hyper::Uri::from_str( - (self.base_url.clone() + "/v2/").as_str() - )); + pub fn is_auth(&self, token: Option<&str>) -> FutureBool { + let url = match hyper::Uri::from_str((self.base_url.clone() + "/v2/").as_str()) { + Ok(url) => url, + Err(e) => return Box::new(futures::future::err(e.into())), + }; let mut req = self.new_request(hyper::Method::GET, url.clone()); if let Some(t) = token { let bearer = format!("Bearer {}", t); - req.headers_mut().append( - header::AUTHORIZATION, - header::HeaderValue::from_str(&bearer).unwrap(), - ); + if let Ok(basic_header) = header::HeaderValue::from_str(&bearer) { + req.headers_mut() + .append(header::AUTHORIZATION, basic_header); + } else { + // TODO: return an error. seems difficult to match the error type for the whole closure + error!("could not parse HeaderValue from '{}'", bearer); + }; }; let freq = self.hclient.request(req); @@ -144,6 +161,6 @@ impl Client { _ => Err(format!("is_auth: wrong HTTP status '{}'", status).into()), } }); - Ok(Box::new(fres)) + Box::new(fres) } } diff --git a/src/v2/blobs.rs b/src/v2/blobs.rs index 7f3d0af6..851249f9 100644 --- a/src/v2/blobs.rs +++ b/src/v2/blobs.rs @@ -8,10 +8,18 @@ pub type FutureBlob = Box, Error = Error>>; impl Client { /// Check if a blob exists. - pub fn has_blob(&self, name: &str, digest: &str) -> Result { + pub fn has_blob(&self, name: &str, digest: &str) -> FutureBool { let url = { let ep = format!("{}/v2/{}/blobs/{}", self.base_url, name, digest); - hyper::Uri::from_str(ep.as_str())? + match hyper::Uri::from_str(ep.as_str()) { + Ok(url) => url, + Err(e) => { + return Box::new(futures::future::err::<_, _>(Error::from(format!( + "failed to parse url from string: {}", + e + )))) + } + } }; let req = self.new_request(hyper::Method::HEAD, url.clone()); let freq = self.hclient.request(req); @@ -29,15 +37,23 @@ impl Client { _ => Ok(false), } }); - Ok(Box::new(fres)) + Box::new(fres) } /// Retrieve blob. - pub fn get_blob(&self, name: &str, digest: &str) -> Result { + pub fn get_blob(&self, name: &str, digest: &str) -> FutureBlob { let cl = self.clone(); let url = { let ep = format!("{}/v2/{}/blobs/{}", self.base_url.clone(), name, digest); - hyper::Uri::from_str(ep.as_str())? + match hyper::Uri::from_str(ep.as_str()) { + Ok(url) => url, + Err(e) => { + return Box::new(futures::future::err::<_, _>(Error::from(format!( + "failed to parse url from string: {}", + e + )))) + } + } }; let req = self.new_request(hyper::Method::GET, url.clone()); let freq = self.hclient.request(req); @@ -86,6 +102,6 @@ impl Client { .concat2() .map_err(|e| format!("get_blob: failed to fetch the whole body: {}", e).into()) }).and_then(|body| Ok(body.into_bytes().to_vec())); - Ok(Box::new(fres)) + Box::new(fres) } } diff --git a/src/v2/catalog.rs b/src/v2/catalog.rs index c3ab35be..2ceb2273 100644 --- a/src/v2/catalog.rs +++ b/src/v2/catalog.rs @@ -14,14 +14,25 @@ struct Catalog { } impl v2::Client { - pub fn get_catalog(&self, paginate: Option) -> Result { + pub fn get_catalog(&self, paginate: Option) -> StreamCatalog { let url = { - let mut s = self.base_url.clone() + "/v2/_catalog"; - if let Some(n) = paginate { - s = s + &format!("?n={}", n); + let suffix = if let Some(n) = paginate { + format!("?n={}", n) + } else { + "".to_string() }; - try!(hyper::Uri::from_str(s.as_str())) + let ep = format!("{}/v2/_catalog{}", self.base_url, suffix); + match hyper::Uri::from_str(ep.as_str()) { + Ok(url) => url, + Err(e) => { + return Box::new(futures::stream::once(Err(format!( + "failed to parse url from string: {}", + e + ).into()))); + } + } }; + let req = self.new_request(hyper::Method::GET, url); let freq = self.hclient.request(req); let fres = freq @@ -41,6 +52,6 @@ impl v2::Client { serde_json::from_slice(&body.into_bytes()).map_err(|e| e.into()) }).map(|cat| futures::stream::iter_ok(cat.repositories.into_iter())) .flatten_stream(); - Ok(Box::new(fres)) + Box::new(fres) } } diff --git a/src/v2/manifest/mod.rs b/src/v2/manifest/mod.rs index 9dfd7e8c..083d9914 100644 --- a/src/v2/manifest/mod.rs +++ b/src/v2/manifest/mod.rs @@ -18,18 +18,20 @@ impl Client { /// /// The name and reference parameters identify the image. /// The reference may be either a tag or digest. - pub fn get_manifest(&self, name: &str, reference: &str) -> Result { - let url = try!(hyper::Uri::from_str(&format!( + pub fn get_manifest(&self, name: &str, reference: &str) -> FutureManifest { + let url = hyper::Uri::from_str(&format!( "{}/v2/{}/manifests/{}", self.base_url.clone(), name, reference - ))); + )).unwrap(); let req = { let mut r = self.new_request(hyper::Method::GET, url.clone()); let mtype = mediatypes::MediaTypes::ManifestV2S2.to_string(); - r.headers_mut() - .append(header::ACCEPT, header::HeaderValue::from_str(&mtype)?); + r.headers_mut().append( + header::ACCEPT, + header::HeaderValue::from_str(&mtype).unwrap(), + ); r }; let freq = self.hclient.request(req); @@ -49,7 +51,7 @@ impl Client { format!("get_manifest: failed to fetch the whole body: {}", e).into() }) }).and_then(|body| Ok(body.into_bytes().to_vec())); - Ok(Box::new(fres)) + Box::new(fres) } /// Check if an image manifest exists. @@ -61,7 +63,7 @@ impl Client { name: &str, reference: &str, mediatypes: Option<&[&str]>, - ) -> Result { + ) -> mediatypes::FutureMediaType { let url = { let ep = format!( "{}/v2/{}/manifests/{}", @@ -69,11 +71,11 @@ impl Client { name, reference ); - try!(hyper::Uri::from_str(ep.as_str())) + hyper::Uri::from_str(ep.as_str()).unwrap() }; let accept_types = match mediatypes { None => vec![mediatypes::MediaTypes::ManifestV2S2.to_mime()], - Some(ref v) => try!(to_mimes(v)), + Some(ref v) => to_mimes(v).unwrap(), }; let req = { let mut r = self.new_request(hyper::Method::HEAD, url.clone()); @@ -107,7 +109,7 @@ impl Client { }; Ok(res) }); - Ok(Box::new(fres)) + Box::new(fres) } } diff --git a/src/v2/mod.rs b/src/v2/mod.rs index 089c3473..d1885d80 100644 --- a/src/v2/mod.rs +++ b/src/v2/mod.rs @@ -20,7 +20,7 @@ //! let dclient = Client::configure(&tcore.handle()) //! .registry("quay.io") //! .build()?; -//! let fetch = dclient.get_manifest("coreos/etcd", "v3.1.0")?; +//! let fetch = dclient.get_manifest("coreos/etcd", "v3.1.0"); //! let manifest = tcore.run(fetch)?; //! # //! # Ok(()) @@ -103,13 +103,19 @@ impl Client { req } - pub fn is_v2_supported(&self) -> Result { + pub fn is_v2_supported(&self) -> FutureBool { let api_header = "Docker-Distribution-API-Version"; let api_version = "registry/2.0"; - let url = try!(hyper::Uri::from_str( - (self.base_url.clone() + "/v2/").as_str() - )); + let url = match hyper::Uri::from_str((self.base_url.clone() + "/v2/").as_str()) { + Ok(url) => url, + Err(e) => { + return Box::new(futures::future::err::<_, _>(Error::from(format!( + "failed to parse url from string: {}", + e + )))) + } + }; let req = self.new_request(hyper::Method::GET, url.clone()); let freq = self.hclient.request(req); let fres = freq @@ -126,7 +132,7 @@ impl Client { }).inspect(|b| { trace!("v2 API supported: {}", b); }); - Ok(Box::new(fres)) + Box::new(fres) } } diff --git a/src/v2/tags.rs b/src/v2/tags.rs index 1b0189e9..cd42d3a8 100644 --- a/src/v2/tags.rs +++ b/src/v2/tags.rs @@ -13,13 +13,13 @@ struct Tags { impl Client { /// List existing tags for an image. - pub fn get_tags(&self, name: &str, paginate: Option) -> Result { + pub fn get_tags(&self, name: &str, paginate: Option) -> StreamTags { let url = { let mut s = format!("{}/v2/{}/tags/list", self.base_url, name); if let Some(n) = paginate { s = s + &format!("?n={}", n); }; - try!(hyper::Uri::from_str(s.as_str())) + hyper::Uri::from_str(s.as_str()).unwrap() }; let req = self.new_request(hyper::Method::GET, url); let freq = self.hclient.request(req); @@ -49,6 +49,6 @@ impl Client { serde_json::from_slice(&body.into_bytes()).map_err(|e| e.into()) }).map(|ts| futures::stream::iter_ok(ts.tags.into_iter())) .flatten_stream(); - Ok(Box::new(fres)) + Box::new(fres) } } diff --git a/tests/mock/api_version.rs b/tests/mock/api_version.rs index 7ec395c8..d8fa8ca5 100644 --- a/tests/mock/api_version.rs +++ b/tests/mock/api_version.rs @@ -25,7 +25,7 @@ fn test_version_check_status_ok() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -50,7 +50,7 @@ fn test_version_check_status_unauth() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -75,7 +75,7 @@ fn test_version_check_status_notfound() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); @@ -100,7 +100,7 @@ fn test_version_check_status_forbidden() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); @@ -122,7 +122,7 @@ fn test_version_check_noheader() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); @@ -147,7 +147,7 @@ fn test_version_check_trailing_slash() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); diff --git a/tests/mock/base_client.rs b/tests/mock/base_client.rs index c930848b..486ea27f 100644 --- a/tests/mock/base_client.rs +++ b/tests/mock/base_client.rs @@ -25,7 +25,7 @@ fn test_base_no_insecure() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); // This relies on the fact that mockito is HTTP-only and // trying to speak TLS to it results in garbage/errors. @@ -52,7 +52,7 @@ fn test_base_useragent() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -81,7 +81,7 @@ fn test_base_custom_useragent() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -108,7 +108,7 @@ fn test_base_no_useragent() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); diff --git a/tests/mock/blobs_download.rs b/tests/mock/blobs_download.rs index b7424da5..2ff1d7c1 100644 --- a/tests/mock/blobs_download.rs +++ b/tests/mock/blobs_download.rs @@ -28,7 +28,7 @@ fn test_blobs_has_layer() { .build() .unwrap(); - let futcheck = dclient.has_blob(name, digest).unwrap(); + let futcheck = dclient.has_blob(name, digest); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -54,7 +54,7 @@ fn test_blobs_hasnot_layer() { .build() .unwrap(); - let futcheck = dclient.has_blob(name, digest).unwrap(); + let futcheck = dclient.has_blob(name, digest); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); diff --git a/tests/mock/catalog.rs b/tests/mock/catalog.rs index 0eed749a..85488d85 100644 --- a/tests/mock/catalog.rs +++ b/tests/mock/catalog.rs @@ -27,7 +27,7 @@ fn test_catalog_simple() { .build() .unwrap(); - let futcheck = dclient.get_catalog(None).unwrap(); + let futcheck = dclient.get_catalog(None); let res = tcore.run(futcheck.collect()).unwrap(); assert_eq!(res, vec!["r1/i1", "r2"]); @@ -67,7 +67,7 @@ fn test_catalog_paginate() { .build() .unwrap(); - let next = dclient.get_catalog(Some(1)).unwrap(); + let next = dclient.get_catalog(Some(1)); let (page1, next) = tcore.run(next.into_future()).ok().unwrap(); assert_eq!(page1, Some("r1/i1".to_owned())); diff --git a/tests/mock/tags.rs b/tests/mock/tags.rs index 58c698f1..bb3907f4 100644 --- a/tests/mock/tags.rs +++ b/tests/mock/tags.rs @@ -29,7 +29,7 @@ fn test_tags_simple() { .build() .unwrap(); - let futcheck = dclient.get_tags(name, None).unwrap(); + let futcheck = dclient.get_tags(name, None); let res = tcore.run(futcheck.collect()).unwrap(); assert_eq!(res, vec!["t1", "t2"]); @@ -72,7 +72,7 @@ fn test_tags_paginate() { .build() .unwrap(); - let next = dclient.get_tags(name, Some(1)).unwrap(); + let next = dclient.get_tags(name, Some(1)); let (page1, next) = tcore.run(next.into_future()).ok().unwrap(); assert_eq!(page1, Some("t1".to_owned())); @@ -106,7 +106,7 @@ fn test_tags_404() { .build() .unwrap(); - let futcheck = dclient.get_tags(name, None).unwrap(); + let futcheck = dclient.get_tags(name, None); let res = tcore.run(futcheck.collect()); assert!(res.is_err()); @@ -135,7 +135,7 @@ fn test_tags_missing_header() { .build() .unwrap(); - let futcheck = dclient.get_tags(name, None).unwrap(); + let futcheck = dclient.get_tags(name, None); let res = tcore.run(futcheck.collect()); assert!(res.is_err()); diff --git a/tests/net/docker_io/mod.rs b/tests/net/docker_io/mod.rs index 385b793e..d872221e 100644 --- a/tests/net/docker_io/mod.rs +++ b/tests/net/docker_io/mod.rs @@ -40,7 +40,7 @@ fn test_dockerio_base() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -57,7 +57,7 @@ fn test_dockerio_insecure() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); diff --git a/tests/net/gcr_io/mod.rs b/tests/net/gcr_io/mod.rs index e860e06c..465f6687 100644 --- a/tests/net/gcr_io/mod.rs +++ b/tests/net/gcr_io/mod.rs @@ -42,7 +42,7 @@ fn test_gcrio_base() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -59,7 +59,7 @@ fn test_gcrio_insecure() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); @@ -77,7 +77,7 @@ fn test_gcrio_get_tags() { .unwrap(); let image = "google_containers/mounttest"; - let fut_tags = dclient.get_tags(image, None).unwrap(); + let fut_tags = dclient.get_tags(image, None); let tags = tcore.run(fut_tags.collect()).unwrap(); let has_version = tags.iter().any(|t| t == "0.2"); @@ -98,9 +98,7 @@ fn test_gcrio_has_manifest() { let image = "google_containers/mounttest"; let tag = "0.2"; let manifest_type = dkregistry::mediatypes::MediaTypes::ManifestV2S1Signed.to_string(); - let fut = dclient - .has_manifest(image, tag, Some(vec![manifest_type.as_str()].as_slice())) - .unwrap(); + let fut = dclient.has_manifest(image, tag, Some(vec![manifest_type.as_str()].as_slice())); let has_manifest = tcore.run(fut).unwrap(); assert_eq!( diff --git a/tests/net/quay_io/mod.rs b/tests/net/quay_io/mod.rs index 973a287f..84d4c638 100644 --- a/tests/net/quay_io/mod.rs +++ b/tests/net/quay_io/mod.rs @@ -43,7 +43,7 @@ fn test_quayio_base() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, true); @@ -60,7 +60,7 @@ fn test_quayio_insecure() { .build() .unwrap(); - let futcheck = dclient.is_v2_supported().unwrap(); + let futcheck = dclient.is_v2_supported(); let res = tcore.run(futcheck).unwrap(); assert_eq!(res, false); @@ -78,7 +78,7 @@ fn test_quayio_get_tags() { .unwrap(); let image = "coreos/alpine-sh"; - let fut_tags = dclient.get_tags(image, None).unwrap(); + let fut_tags = dclient.get_tags(image, None); let tags = tcore.run(fut_tags.collect()).unwrap(); let has_version = tags.iter().any(|t| t == "latest"); @@ -98,7 +98,7 @@ fn test_quayio_has_manifest() { let image = "coreos/alpine-sh"; let reference = "latest"; - let fut = dclient.has_manifest(image, reference, None).unwrap(); + let fut = dclient.has_manifest(image, reference, None); let has_manifest = tcore.run(fut).unwrap(); assert_eq!(has_manifest, Some(MediaTypes::ManifestV2S1Signed)); @@ -117,7 +117,7 @@ fn test_quayio_has_no_manifest() { let image = "coreos/alpine-sh"; let reference = "clearly_bogus"; - let fut = dclient.has_manifest(image, reference, None).unwrap(); + let fut = dclient.has_manifest(image, reference, None); let has_manifest = tcore.run(fut).unwrap(); assert_eq!(has_manifest, None); From c42c3205fec8c87026af91f9c639320d9ab4baea Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Wed, 24 Oct 2018 23:08:02 +0200 Subject: [PATCH 2/4] examples/*: streamline, refactor, and rewrite to use future chaining --- Cargo.toml | 1 + examples/checkregistry.rs | 8 +- examples/common/mod.rs | 42 ++++++++++ examples/image.rs | 161 ++++++++++++++++++++------------------ examples/login.rs | 68 +++++++--------- examples/tags.rs | 58 ++++++-------- examples/trace.rs | 138 +++++++++++++++++--------------- 7 files changed, 259 insertions(+), 217 deletions(-) create mode 100644 examples/common/mod.rs diff --git a/Cargo.toml b/Cargo.toml index c0ccdafe..da13cedd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,7 @@ strum = "0.11" strum_macros = "0.11" tar = "0.4" tokio-core = "0.1" +dirs = "1.0" [dev-dependencies] env_logger = "0.5" diff --git a/examples/checkregistry.rs b/examples/checkregistry.rs index ae391b11..9440a14b 100644 --- a/examples/checkregistry.rs +++ b/examples/checkregistry.rs @@ -4,8 +4,6 @@ extern crate tokio_core; use std::{boxed, error}; use tokio_core::reactor::Core; -type Result = std::result::Result>; - fn main() { let registry = match std::env::args().nth(1) { Some(x) => x, @@ -20,7 +18,7 @@ fn main() { }; } -fn run(host: &str) -> Result { +fn run(host: &str) -> Result> { let mut tcore = try!(Core::new()); let dclient = try!( dkregistry::v2::Client::configure(&tcore.handle()) @@ -28,12 +26,12 @@ fn run(host: &str) -> Result { .insecure_registry(false) .build() ); - let futcheck = try!(dclient.is_v2_supported()); + let futcheck = dclient.is_v2_supported(); let supported = try!(tcore.run(futcheck)); match supported { false => println!("{} does NOT support v2", host), true => println!("{} supports v2", host), } - return Ok(supported); + Ok(supported) } diff --git a/examples/common/mod.rs b/examples/common/mod.rs new file mode 100644 index 00000000..a1287c08 --- /dev/null +++ b/examples/common/mod.rs @@ -0,0 +1,42 @@ +extern crate dkregistry; +extern crate futures; + +use futures::prelude::*; + +pub fn authenticate_client<'a>( + client: &'a mut dkregistry::v2::Client, + login_scope: &'a str, +) -> impl futures::future::Future +{ + futures::future::ok::<_, dkregistry::errors::Error>(client) + .and_then(|dclient| { + dclient.is_v2_supported().and_then(|v2_supported| { + if !v2_supported { + Err("API v2 not supported".into()) + } else { + Ok(dclient) + } + }) + }).and_then(|dclient| { + dclient.is_auth(None).and_then(|is_auth| { + if is_auth { + Err("no login performed, but already authenticated".into()) + } else { + Ok(dclient) + } + }) + }).and_then(move |dclient| { + dclient.login(&[&login_scope]).and_then(move |token| { + dclient + .is_auth(Some(token.token())) + .and_then(move |is_auth| { + if !is_auth { + Err("login failed".into()) + } else { + println!("logged in!"); + Ok(dclient.set_token(Some(token.token()))) + } + }) + }) + }) +} diff --git a/examples/image.rs b/examples/image.rs index 068f9514..f88f7a9a 100644 --- a/examples/image.rs +++ b/examples/image.rs @@ -1,15 +1,17 @@ +extern crate dirs; extern crate dkregistry; extern crate futures; extern crate serde_json; extern crate tokio_core; use dkregistry::{reference, render}; +use futures::prelude::*; +use std::result::Result; +use std::str::FromStr; use std::{boxed, env, error, fs, io}; use tokio_core::reactor::Core; -use std::str::FromStr; - -type Result = std::result::Result>; +mod common; fn main() { let dkr_ref = match std::env::args().nth(1) { @@ -22,7 +24,7 @@ fn main() { let mut user = None; let mut password = None; - let home = env::home_dir().unwrap_or("/root".into()); + let home = dirs::home_dir().unwrap(); let cfg = fs::File::open(home.join(".docker/config.json")); if let Ok(fp) = cfg { let creds = dkregistry::get_credentials(io::BufReader::new(fp), ®istry); @@ -51,87 +53,90 @@ fn main() { }; } -fn run(dkr_ref: &reference::Reference, user: Option, passwd: Option) -> Result<()> { - let image = dkr_ref.repository(); - let version = dkr_ref.version(); - +fn run( + dkr_ref: &reference::Reference, + user: Option, + passwd: Option, +) -> Result<(), boxed::Box> { let mut tcore = try!(Core::new()); - let mut dclient = try!( - dkregistry::v2::Client::configure(&tcore.handle()) - .registry(&dkr_ref.registry()) - .insecure_registry(false) - .username(user) - .password(passwd) - .build() - ); - - let futcheck = try!(dclient.is_v2_supported()); - let supported = try!(tcore.run(futcheck)); - if !supported { - return Err("API v2 not supported".into()); - } - let fut_token = try!(dclient.login(&[&format!("repository:{}:pull", image)])); - let token_auth = try!(tcore.run(fut_token)); - - let futauth = try!(dclient.is_auth(Some(token_auth.token()))); - if !try!(tcore.run(futauth)) { - return Err("login failed".into()); - } + let mut client = dkregistry::v2::Client::configure(&tcore.handle()) + .registry(&dkr_ref.registry()) + .insecure_registry(false) + .username(user) + .password(passwd) + .build()?; - dclient.set_token(Some(token_auth.token())); - - let fut_hasmanif = dclient.has_manifest(&image, &version, None)?; - let manifest_kind = try!(tcore.run(fut_hasmanif)?.ok_or("no manifest found")); - - let fut_manif = dclient.get_manifest(&image, &version)?; - let body = tcore.run(fut_manif)?; + let image = dkr_ref.repository(); + let login_scope = format!("repository:{}:pull", image); + let version = dkr_ref.version(); - let layers = match manifest_kind { - dkregistry::mediatypes::MediaTypes::ManifestV2S1Signed => { - let m: dkregistry::v2::manifest::ManifestSchema1Signed = - try!(serde_json::from_slice(body.as_slice())); - m.get_layers() - } - dkregistry::mediatypes::MediaTypes::ManifestV2S2 => { - let m: dkregistry::v2::manifest::ManifestSchema2 = - try!(serde_json::from_slice(body.as_slice())); - m.get_layers() - } - _ => return Err("unknown format".into()), + let futures = common::authenticate_client(&mut client, &login_scope) + .and_then(|dclient| { + dclient + .has_manifest(&image, &version, None) + .and_then(move |manifest_option| Ok((dclient, manifest_option))) + .and_then(|(dclient, manifest_option)| match manifest_option { + None => Err(format!("{}:{} doesn't have a manifest", &image, &version).into()), + + Some(manifest_kind) => Ok((dclient, manifest_kind)), + }) + }).and_then(|(dclient, manifest_kind)| { + let image = image.clone(); + dclient.get_manifest(&image, &version).and_then( + move |manifest_body| match manifest_kind { + dkregistry::mediatypes::MediaTypes::ManifestV2S1Signed => { + let m: dkregistry::v2::manifest::ManifestSchema1Signed = match + serde_json::from_slice(manifest_body.as_slice()) { + Ok(json) => json, + Err(e) => return Err(e.into()), + + }; + Ok((dclient, m.get_layers())) + } + dkregistry::mediatypes::MediaTypes::ManifestV2S2 => { + let m: dkregistry::v2::manifest::ManifestSchema2 = + match serde_json::from_slice(manifest_body.as_slice()) { + Ok(json) => json, + Err(e) => return Err(e.into()), + }; + Ok((dclient, m.get_layers())) + } + _ => Err("unknown format".into()), + }, + ) + }).and_then(|(dclient, layers)| { + let image = image.clone(); + + println!("{} -> got {} layer(s)", &image, layers.len(),); + + futures::stream::iter_ok::<_, dkregistry::errors::Error>(layers) + .and_then(move |layer| { + let get_blob_future = dclient.get_blob(&image, &layer); + get_blob_future.inspect(move |blob| { + println!("Layer {}, got {} bytes.\n", layer, blob.len()); + }) + }).collect() + }); + + let blobs = match tcore.run(futures) { + Ok(blobs) => blobs, + Err(e) => return Err(Box::new(e)), }; - println!( - "{} -> got {} layer(s), saving to directory {:?}", - image, - layers.len(), - version - ); - std::fs::create_dir(&version)?; - let mut blobs: Vec> = vec![]; - - for (i, digest) in layers.iter().enumerate() { - let fut_presence = dclient.has_blob(&image, &digest)?; - let has_blob = tcore.run(fut_presence)?; - if !has_blob { - return Err(format!("missing layer {}", digest).into()); - } + println!("Downloaded {} layers", blobs.len()); - println!("Downloading layer {}...", digest); - let fut_out = dclient.get_blob(&image, &digest)?; - let out = tcore.run(fut_out)?; - println!( - "Layer {}/{}, got {} bytes.\n", - i + 1, - layers.len(), - out.len() - ); - blobs.push(out); + let path = &format!("{}:{}", &image, &version).replace("/", "_"); + let path = std::path::Path::new(&path); + if path.exists() { + return Err(format!("path {:?} already exists, exiting", &path).into()); } + // TODO: use async io + std::fs::create_dir(&path).unwrap(); + let can_path = path.canonicalize().unwrap(); + + println!("Unpacking layers to {:?}", &can_path); + let r = render::unpack(&blobs, &can_path).unwrap(); - let can_path = std::fs::canonicalize(&version)?; - let r = render::unpack(&blobs, &can_path); - println!("{:?}", r); - r?; - Ok(()) + Ok(r) } diff --git a/examples/login.rs b/examples/login.rs index e5a976c6..53755247 100644 --- a/examples/login.rs +++ b/examples/login.rs @@ -1,11 +1,14 @@ extern crate dkregistry; +extern crate futures; extern crate tokio_core; +mod common; + +use futures::prelude::*; +use std::result::Result; use std::{boxed, error}; use tokio_core::reactor::Core; -type Result = std::result::Result>; - fn main() { let registry = match std::env::args().nth(1) { Some(x) => x, @@ -29,43 +32,28 @@ fn main() { }; } -fn run(host: &str, user: Option, passwd: Option) -> Result<()> { - let mut tcore = try!(Core::new()); - let dclient = try!( - dkregistry::v2::Client::configure(&tcore.handle()) - .registry(host) - .insecure_registry(false) - .username(user) - .password(passwd) - .build() - ); - - let futcheck = try!(dclient.is_v2_supported()); - let supported = try!(tcore.run(futcheck)); - if !supported { - return Err("API v2 not supported".into()); +fn run( + host: &str, + user: Option, + passwd: Option, +) -> Result<(), boxed::Box> { + let mut tcore = Core::new()?; + + let mut client = dkregistry::v2::Client::configure(&tcore.handle()) + .registry(host) + .insecure_registry(false) + .username(user) + .password(passwd) + .build()?; + + let login_scope = ""; + + let futures = common::authenticate_client(&mut client, &login_scope) + .and_then(|dclient| dclient.is_v2_supported()); + + match tcore.run(futures) { + Ok(login_successful) if login_successful => Ok(()), + Err(e) => Err(Box::new(e)), + _ => Err("Login unsucessful".into()), } - - let futauth = try!(dclient.is_auth(None)); - let logged_in = try!(tcore.run(futauth)); - if logged_in { - return Err("no login performed, but already authenticated".into()); - } - - let fut_token = try!(dclient.login(&[])); - let token = try!(tcore.run(fut_token)); - - let futauth = try!(dclient.is_auth(Some(token.token()))); - let done = try!(tcore.run(futauth)); - - match done { - false => return Err("login failed".into()), - true => println!("logged in!",), - } - let futcheck = try!(dclient.is_v2_supported()); - if !try!(tcore.run(futcheck)) { - return Err("API check failed after login".into()); - }; - - return Ok(()); } diff --git a/examples/tags.rs b/examples/tags.rs index f3b9f57e..11058d6a 100644 --- a/examples/tags.rs +++ b/examples/tags.rs @@ -2,12 +2,13 @@ extern crate dkregistry; extern crate futures; extern crate tokio_core; -use futures::stream::Stream; +mod common; + +use futures::prelude::*; +use std::result::Result; use std::{boxed, error}; use tokio_core::reactor::Core; -type Result = std::result::Result>; - fn main() { let registry = match std::env::args().nth(1) { Some(x) => x, @@ -37,37 +38,30 @@ fn main() { }; } -fn run(host: &str, user: Option, passwd: Option, image: &str) -> Result<()> { - let mut tcore = try!(Core::new()); - let mut dclient = try!( - dkregistry::v2::Client::configure(&tcore.handle()) - .registry(host) - .insecure_registry(false) - .username(user) - .password(passwd) - .build() - ); +fn run( + host: &str, + user: Option, + passwd: Option, + image: &str, +) -> Result<(), boxed::Box> { + let mut tcore = Core::new()?; + let mut client = dkregistry::v2::Client::configure(&tcore.handle()) + .registry(host) + .insecure_registry(false) + .username(user) + .password(passwd) + .build()?; - let futcheck = try!(dclient.is_v2_supported()); - let supported = try!(tcore.run(futcheck)); - if !supported { - return Err("API v2 not supported".into()); - } + let login_scope = format!("repository:{}:pull", image); - let fut_token = try!(dclient.login(&[&format!("repository:{}:pull", image)])); - let token_auth = try!(tcore.run(fut_token)); + let futures = common::authenticate_client(&mut client, &login_scope) + .and_then(|dclient| dclient.get_tags(&image, Some(7)).collect()); - let futauth = try!(dclient.is_auth(Some(token_auth.token()))); - if !try!(tcore.run(futauth)) { - return Err("login failed".into()); + match tcore.run(futures) { + Ok(tags) => { + println!("{:?}", tags); + Ok(()) + } + Err(e) => Err(Box::new(e)), } - - dclient.set_token(Some(token_auth.token())); - - let fut_tags = dclient.get_tags(image, Some(7))?; - let tags = tcore.run(fut_tags.collect()); - - println!("{:?}", tags); - - return Ok(()); } diff --git a/examples/trace.rs b/examples/trace.rs index 9452ea94..2ae17e25 100644 --- a/examples/trace.rs +++ b/examples/trace.rs @@ -1,3 +1,4 @@ +extern crate dirs; extern crate dkregistry; extern crate env_logger; extern crate futures; @@ -5,14 +6,14 @@ extern crate log; extern crate serde_json; extern crate tokio_core; +mod common; + use dkregistry::reference; +use futures::prelude::*; +use std::str::FromStr; use std::{boxed, env, error, fs, io}; use tokio_core::reactor::Core; -use std::str::FromStr; - -type Result = std::result::Result>; - fn main() { let dkr_ref = match std::env::args().nth(1) { Some(ref x) => reference::Reference::from_str(x), @@ -24,7 +25,7 @@ fn main() { let mut user = None; let mut password = None; - let home = env::home_dir().unwrap_or("/root".into()); + let home = dirs::home_dir().unwrap(); let cfg = fs::File::open(home.join(".docker/config.json")); if let Ok(fp) = cfg { let creds = dkregistry::get_credentials(io::BufReader::new(fp), ®istry); @@ -53,70 +54,83 @@ fn main() { }; } -fn run(dkr_ref: &reference::Reference, user: Option, passwd: Option) -> Result<()> { +fn run( + dkr_ref: &reference::Reference, + user: Option, + passwd: Option, +) -> Result<(), boxed::Box> { env_logger::Builder::new() .filter(Some("dkregistry"), log::LevelFilter::Trace) .filter(Some("trace"), log::LevelFilter::Trace) .try_init()?; + let image = dkr_ref.repository(); let version = dkr_ref.version(); - - let mut tcore = try!(Core::new()); - let mut dclient = try!( - dkregistry::v2::Client::configure(&tcore.handle()) - .registry(&dkr_ref.registry()) - .insecure_registry(false) - .username(user) - .password(passwd) - .build() - ); - - let futcheck = try!(dclient.is_v2_supported()); - let supported = try!(tcore.run(futcheck)); - if !supported { - return Err("API v2 not supported".into()); - } - - let fut_token = try!(dclient.login(&[&format!("repository:{}:pull", image)])); - let token_auth = try!(tcore.run(fut_token)); - - let futauth = try!(dclient.is_auth(Some(token_auth.token()))); - if !try!(tcore.run(futauth)) { - return Err("login failed".into()); - } - - dclient.set_token(Some(token_auth.token())); - - let fut_hasmanif = dclient.has_manifest(&image, &version, None)?; - let manifest_kind = try!(tcore.run(fut_hasmanif)?.ok_or("no manifest found")); - - let fut_manif = dclient.get_manifest(&image, &version)?; - let body = tcore.run(fut_manif)?; - - let layers = match manifest_kind { - dkregistry::mediatypes::MediaTypes::ManifestV2S1Signed => { - let m: dkregistry::v2::manifest::ManifestSchema1Signed = - try!(serde_json::from_slice(body.as_slice())); - m.get_layers() - } - dkregistry::mediatypes::MediaTypes::ManifestV2S2 => { - let m: dkregistry::v2::manifest::ManifestSchema2 = - try!(serde_json::from_slice(body.as_slice())); - m.get_layers() - } - _ => return Err("unknown format".into()), + let mut tcore = Core::new()?; + + let mut client = dkregistry::v2::Client::configure(&tcore.handle()) + .registry(&dkr_ref.registry()) + .insecure_registry(false) + .username(user) + .password(passwd) + .build()?; + + let login_scope = ""; + + let futures = common::authenticate_client(&mut client, &login_scope) + .and_then(|dclient| { + dclient + .has_manifest(&image, &version, None) + .and_then(move |manifest_option| Ok((dclient, manifest_option))) + .and_then(|(dclient, manifest_option)| match manifest_option { + None => { + return Err( + format!("{}:{} doesn't have a manifest", &image, &version).into() + ) + } + + Some(manifest_kind) => Ok((dclient, manifest_kind)), + }) + }).and_then(|(dclient, manifest_kind)| { + let image = image.clone(); + dclient + .get_manifest(&image, &version) + .and_then(move |manifest_body| { + let layers = match manifest_kind { + dkregistry::mediatypes::MediaTypes::ManifestV2S1Signed => { + let m: dkregistry::v2::manifest::ManifestSchema1Signed = + serde_json::from_slice(manifest_body.as_slice()).unwrap(); + m.get_layers() + } + dkregistry::mediatypes::MediaTypes::ManifestV2S2 => { + let m: dkregistry::v2::manifest::ManifestSchema2 = + serde_json::from_slice(manifest_body.as_slice()).unwrap(); + m.get_layers() + } + _ => return Err("unknown format".into()), + }; + Ok((dclient, layers)) + }) + }).and_then(|(dclient, layers)| { + let image = image.clone(); + + println!("{} -> got {} layer(s)", &image, layers.len(),); + + futures::stream::iter_ok::<_, dkregistry::errors::Error>(layers) + .and_then(move |layer| { + let get_blob_future = dclient.get_blob(&image, &layer); + get_blob_future.inspect(move |blob| { + println!("Layer {}, got {} bytes.\n", layer, blob.len()); + }) + }).collect() + }); + + let blobs = match tcore.run(futures) { + Ok(blobs) => blobs, + Err(e) => return Err(Box::new(e)), }; - for digest in layers { - let fut_presence = dclient.has_blob(&image, &digest)?; - let has_blob = tcore.run(fut_presence)?; - if !has_blob { - return Err(format!("missing layer {}", digest).into()); - } - - let fut_out = dclient.get_blob(&image, &digest)?; - let _out = tcore.run(fut_out)?; - } + println!("Downloaded {} layers", blobs.len()); - return Ok(()); + Ok(()) } From 8f0dabe7de57d9b78aa5bec4f4253799aaf030e8 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Thu, 25 Oct 2018 11:42:53 +0200 Subject: [PATCH 3/4] API: resolve all remaining `unwrap()`s `git grep -e 'unwrap()' --and --not -e '^//' -n src/` doesn't indicate any occurences left. --- src/mediatypes.rs | 10 ++--- src/v2/auth.rs | 31 ++++++++++++---- src/v2/blobs.rs | 20 ++++++++-- src/v2/catalog.rs | 16 +++++--- src/v2/manifest/mod.rs | 84 +++++++++++++++++++++++++++++++++++------- src/v2/mod.rs | 20 ++++++---- src/v2/tags.rs | 21 +++++++++-- 7 files changed, 158 insertions(+), 44 deletions(-) diff --git a/src/mediatypes.rs b/src/mediatypes.rs index cc1998b8..7f102001 100644 --- a/src/mediatypes.rs +++ b/src/mediatypes.rs @@ -65,16 +65,16 @@ impl MediaTypes { _ => bail!("unknown mediatype {:?}", mtype), } } - pub fn to_mime(&self) -> mime::Mime { + pub fn to_mime(&self) -> Result { match self { - &MediaTypes::ApplicationJson => mime::APPLICATION_JSON, + &MediaTypes::ApplicationJson => Ok(mime::APPLICATION_JSON), ref m => { if let Some(s) = m.get_str("Sub") { - ("application/".to_string() + s).parse().unwrap() + ("application/".to_string() + s).parse() } else { - "application/star".parse().unwrap() + "application/star".parse() } } - } + }.map_err(|e| Error::from(e.to_string())) } } diff --git a/src/v2/auth.rs b/src/v2/auth.rs index ab88297d..339924c8 100644 --- a/src/v2/auth.rs +++ b/src/v2/auth.rs @@ -36,7 +36,14 @@ impl Client { } } }; - let req = self.new_request(hyper::Method::GET, url); + let req = match self.new_request(hyper::Method::GET, url) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + }; let freq = self.hclient.request(req); let www_auth = freq .from_err() @@ -104,12 +111,14 @@ impl Client { .headers_mut() .append(header::AUTHORIZATION, basic_header); } else { - // TODO: return an error. seems difficult to match the error type for the whole closure - error!("could not parse HeaderValue from '{}'", basic); + let msg = format!("could not parse HeaderValue from '{}'", basic); + error!("{}", msg); + // TODO: return an error. seems difficult to match the error type for the whole closure }; }; subclient.request(auth_req).map_err(|e| e.into()) - }).and_then(|r| { + }) + .and_then(|r| { let status = r.status(); trace!("Got status {}", status); match status { @@ -135,15 +144,23 @@ impl Client { Ok(url) => url, Err(e) => return Box::new(futures::future::err(e.into())), }; - let mut req = self.new_request(hyper::Method::GET, url.clone()); + let mut req = match self.new_request(hyper::Method::GET, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err(Error::from(msg))); + } + }; if let Some(t) = token { let bearer = format!("Bearer {}", t); if let Ok(basic_header) = header::HeaderValue::from_str(&bearer) { req.headers_mut() .append(header::AUTHORIZATION, basic_header); } else { - // TODO: return an error. seems difficult to match the error type for the whole closure - error!("could not parse HeaderValue from '{}'", bearer); + let msg = format!("could not parse HeaderValue from '{}'", bearer); + error!("{}", msg); + return Box::new(futures::future::err(Error::from(msg))); }; }; diff --git a/src/v2/blobs.rs b/src/v2/blobs.rs index 851249f9..37ca67b4 100644 --- a/src/v2/blobs.rs +++ b/src/v2/blobs.rs @@ -21,7 +21,14 @@ impl Client { } } }; - let req = self.new_request(hyper::Method::HEAD, url.clone()); + let req = match self.new_request(hyper::Method::HEAD, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + }; let freq = self.hclient.request(req); let fres = freq .from_err() @@ -51,11 +58,18 @@ impl Client { return Box::new(futures::future::err::<_, _>(Error::from(format!( "failed to parse url from string: {}", e - )))) + )))); } } }; - let req = self.new_request(hyper::Method::GET, url.clone()); + let req = match self.new_request(hyper::Method::GET, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + }; let freq = self.hclient.request(req); let fres = freq .from_err() diff --git a/src/v2/catalog.rs b/src/v2/catalog.rs index 2ceb2273..37dc36c7 100644 --- a/src/v2/catalog.rs +++ b/src/v2/catalog.rs @@ -25,15 +25,21 @@ impl v2::Client { match hyper::Uri::from_str(ep.as_str()) { Ok(url) => url, Err(e) => { - return Box::new(futures::stream::once(Err(format!( - "failed to parse url from string: {}", - e - ).into()))); + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::stream::once::<_, Error>(Err(Error::from(msg)))); } } }; - let req = self.new_request(hyper::Method::GET, url); + let req = match self.new_request(hyper::Method::GET, url) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::stream::once::<_, Error>(Err(Error::from(msg)))); + } + }; let freq = self.hclient.request(req); let fres = freq .from_err() diff --git a/src/v2/manifest/mod.rs b/src/v2/manifest/mod.rs index 083d9914..759df413 100644 --- a/src/v2/manifest/mod.rs +++ b/src/v2/manifest/mod.rs @@ -19,20 +19,41 @@ impl Client { /// The name and reference parameters identify the image. /// The reference may be either a tag or digest. pub fn get_manifest(&self, name: &str, reference: &str) -> FutureManifest { - let url = hyper::Uri::from_str(&format!( + let url = match hyper::Uri::from_str(&format!( "{}/v2/{}/manifests/{}", self.base_url.clone(), name, reference - )).unwrap(); + )) { + Ok(url) => url, + Err(e) => { + let msg = format!("failed to parse Uri from str: {}", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + }; let req = { - let mut r = self.new_request(hyper::Method::GET, url.clone()); + let mut req = match self.new_request(hyper::Method::GET, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err(Error::from(msg))); + } + }; let mtype = mediatypes::MediaTypes::ManifestV2S2.to_string(); - r.headers_mut().append( + req.headers_mut().append( header::ACCEPT, - header::HeaderValue::from_str(&mtype).unwrap(), + match header::HeaderValue::from_str(&mtype) { + Ok(headervalue) => headervalue, + Err(e) => { + let msg = format!("failed to parse HeaderValue from str: {}:", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + }, ); - r + req }; let freq = self.hclient.request(req); let fres = freq @@ -71,19 +92,48 @@ impl Client { name, reference ); - hyper::Uri::from_str(ep.as_str()).unwrap() + match hyper::Uri::from_str(ep.as_str()) { + Ok(url) => url, + Err(e) => { + let msg = format!("failed to parse Uri from str: {}", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + } }; - let accept_types = match mediatypes { - None => vec![mediatypes::MediaTypes::ManifestV2S2.to_mime()], - Some(ref v) => to_mimes(v).unwrap(), + let accept_types = match { + match mediatypes { + None => if let Ok(m) = mediatypes::MediaTypes::ManifestV2S2.to_mime() { + Ok(vec![m]) + } else { + Err(Error::from("to_mime failed")) + }, + Some(ref v) => to_mimes(v), + } + } { + Ok(x) => x, + Err(e) => { + return Box::new(futures::future::err::<_, _>(Error::from(format!( + "failed to match mediatypes: {}", + e + )))); + } }; + let req = { - let mut r = self.new_request(hyper::Method::HEAD, url.clone()); + let mut req = match self.new_request(hyper::Method::HEAD, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err(Error::from(msg))); + } + }; for v in accept_types { let _ = header::HeaderValue::from_str(&v.to_string()) - .map(|hval| r.headers_mut().append(hyper::header::ACCEPT, hval)); + .map(|hval| req.headers_mut().append(hyper::header::ACCEPT, hval)); } - r + req }; let freq = self.hclient.request(req); let fres = freq @@ -119,7 +169,13 @@ fn to_mimes(v: &[&str]) -> Result> { .filter_map(|x| { let mtype = mediatypes::MediaTypes::from_str(x); match mtype { - Ok(m) => Some(m.to_mime()), + Ok(m) => Some(match m.to_mime() { + Ok(mime) => mime, + Err(e) => { + error!("to_mime failed: {}", e); + return None; + } + }), _ => None, } }).collect(); diff --git a/src/v2/mod.rs b/src/v2/mod.rs index d1885d80..ac1e22a3 100644 --- a/src/v2/mod.rs +++ b/src/v2/mod.rs @@ -78,29 +78,28 @@ impl Client { Config::default(handle) } - fn new_request(&self, method: hyper::Method, url: hyper::Uri) -> hyper::Request { - // TODO(lucab): get rid of all unwraps here. + fn new_request(&self, method: hyper::Method, url: hyper::Uri) -> Result> { let mut req = hyper::Request::default(); *req.method_mut() = method; *req.uri_mut() = url; req.headers_mut().append( header::HOST, - header::HeaderValue::from_str(&self.index).unwrap(), + header::HeaderValue::from_str(&self.index)?, ); if let Some(ref t) = self.token { let bearer = format!("Bearer {}", t); req.headers_mut().append( header::AUTHORIZATION, - header::HeaderValue::from_str(&bearer).unwrap(), + header::HeaderValue::from_str(&bearer)?, ); }; if let Some(ref ua) = self.user_agent { req.headers_mut().append( header::USER_AGENT, - header::HeaderValue::from_str(ua).unwrap(), + header::HeaderValue::from_str(ua)?, ); }; - req + Ok(req) } pub fn is_v2_supported(&self) -> FutureBool { @@ -116,7 +115,14 @@ impl Client { )))) } }; - let req = self.new_request(hyper::Method::GET, url.clone()); + let req = match self.new_request(hyper::Method::GET, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::future::err::<_, _>(Error::from(msg))); + } + }; let freq = self.hclient.request(req); let fres = freq .from_err() diff --git a/src/v2/tags.rs b/src/v2/tags.rs index cd42d3a8..3f9c257b 100644 --- a/src/v2/tags.rs +++ b/src/v2/tags.rs @@ -1,4 +1,4 @@ -use futures::{self, Stream}; +use futures::prelude::*; use hyper::{self, header}; use v2::*; @@ -19,9 +19,24 @@ impl Client { if let Some(n) = paginate { s = s + &format!("?n={}", n); }; - hyper::Uri::from_str(s.as_str()).unwrap() + match hyper::Uri::from_str(s.as_str()) { + Ok(url) => url, + Err(e) => { + return Box::new(futures::stream::once(Err(format!( + "failed to parse url from string: {}", + e + ).into()))); + } + } + }; + let req = match self.new_request(hyper::Method::GET, url.clone()) { + Ok(r) => r, + Err(e) => { + let msg = format!("new_request failed: {}", e); + error!("{}", msg); + return Box::new(futures::stream::once(Err(msg.into()))); + } }; - let req = self.new_request(hyper::Method::GET, url); let freq = self.hclient.request(req); let fres = freq .from_err() From edfc08bece70260185c4e618b3a4f6e384db4d12 Mon Sep 17 00:00:00 2001 From: Stefan Junker Date: Fri, 26 Oct 2018 11:23:28 +0200 Subject: [PATCH 4/4] examples/tags: add loop demo --- examples/tags.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/examples/tags.rs b/examples/tags.rs index 11058d6a..4d6e6807 100644 --- a/examples/tags.rs +++ b/examples/tags.rs @@ -55,13 +55,16 @@ fn run( let login_scope = format!("repository:{}:pull", image); let futures = common::authenticate_client(&mut client, &login_scope) - .and_then(|dclient| dclient.get_tags(&image, Some(7)).collect()); + .and_then(|dclient| dclient.get_tags(&image, Some(7)).collect()) + .and_then(|tags| { + for tag in tags { + println!("{:?}", tag); + } + Ok(()) + }); match tcore.run(futures) { - Ok(tags) => { - println!("{:?}", tags); - Ok(()) - } + Ok(_) => Ok(()), Err(e) => Err(Box::new(e)), } }