1use crate::errors::*;
31use futures::prelude::*;
32use reqwest::{Method, StatusCode, Url};
33
34mod config;
35pub use self::config::Config;
36
37mod catalog;
38
39mod auth;
40pub use auth::WwwHeaderParseError;
41
42pub mod manifest;
43
44mod tags;
45
46mod blobs;
47
48mod content_digest;
49pub(crate) use self::content_digest::ContentDigest;
50pub use self::content_digest::ContentDigestError;
51
52#[derive(Clone, Debug)]
54pub struct Client {
55 base_url: String,
56 credentials: Option<(String, String)>,
57 index: String,
58 user_agent: Option<String>,
59 auth: Option<auth::Auth>,
60 client: reqwest::Client,
61}
62
63impl Client {
64 pub fn configure() -> Config {
65 Config::default()
66 }
67
68 pub async fn ensure_v2_registry(self) -> Result<Self> {
70 if !self.is_v2_supported().await? {
71 Err(Error::V2NotSupported)
72 } else {
73 Ok(self)
74 }
75 }
76
77 pub async fn is_v2_supported(&self) -> Result<bool> {
79 match self.is_v2_supported_and_authorized().await {
80 Ok((v2_supported, _)) => Ok(v2_supported),
81 Err(crate::Error::UnexpectedHttpStatus(_)) => Ok(false),
82 Err(e) => Err(e),
83 }
84 }
85
86 pub async fn is_v2_supported_and_authorized(&self) -> Result<(bool, bool)> {
89 let api_header = "Docker-Distribution-API-Version";
90 let api_version = "registry/2.0";
91
92 let v2_endpoint = format!("{}/v2/", self.base_url);
94 let request = reqwest::Url::parse(&v2_endpoint).map(|url| {
95 trace!("GET {:?}", url);
96 self.build_reqwest(Method::GET, url)
97 })?;
98
99 let response = request.send().await?;
100
101 let b = match (response.status(), response.headers().get(api_header)) {
102 (StatusCode::OK, Some(x)) => Ok((x == api_version, true)),
103 (StatusCode::UNAUTHORIZED, Some(x)) => Ok((x == api_version, false)),
104 (s, v) => {
105 trace!("Got unexpected status {}, header version {:?}", s, v);
106 return Err(crate::Error::UnexpectedHttpStatus(s));
107 }
108 };
109
110 b
111 }
112
113 fn build_reqwest(&self, method: Method, url: Url) -> reqwest::RequestBuilder {
115 let mut builder = self.client.request(method, url);
116
117 if let Some(auth) = &self.auth {
118 builder = auth.add_auth_headers(builder);
119 };
120
121 if let Some(ua) = &self.user_agent {
122 builder = builder.header(reqwest::header::USER_AGENT, ua.as_str());
123 };
124
125 builder
126 }
127}
128
129#[derive(Debug, Default, Deserialize, Serialize)]
130struct ApiError {
131 code: String,
132 message: String,
133 detail: String,
134}
135
136#[derive(Debug, Default, Deserialize, Serialize)]
137struct Errors {
138 errors: Vec<ApiError>,
139}