use futures::prelude::*;
use log::trace;
use reqwest::{Method, StatusCode, Url};
use serde::{Deserialize, Serialize};
use crate::{errors::*, mediatypes::MediaTypes};
mod config;
pub use self::config::Config;
mod catalog;
mod auth;
pub use auth::WwwHeaderParseError;
pub mod manifest;
mod tags;
mod blobs;
mod content_digest;
pub(crate) use self::content_digest::ContentDigest;
pub use self::content_digest::ContentDigestError;
#[derive(Clone, Debug)]
pub struct Client {
base_url: String,
credentials: Option<(String, String)>,
user_agent: Option<String>,
auth: Option<auth::Auth>,
client: reqwest::Client,
accepted_types: Vec<(MediaTypes, Option<f64>)>,
}
impl Client {
pub fn configure() -> Config {
Config::default()
}
pub async fn ensure_v2_registry(self) -> Result<Self> {
if !self.is_v2_supported().await? {
Err(Error::V2NotSupported)
} else {
Ok(self)
}
}
pub async fn is_v2_supported(&self) -> Result<bool> {
match self.is_v2_supported_and_authorized().await {
Ok((v2_supported, _)) => Ok(v2_supported),
Err(crate::Error::UnexpectedHttpStatus(_)) => Ok(false),
Err(e) => Err(e),
}
}
pub async fn is_v2_supported_and_authorized(&self) -> Result<(bool, bool)> {
let api_header = "Docker-Distribution-API-Version";
let api_version = "registry/2.0";
let v2_endpoint = format!("{}/v2/", self.base_url);
let request = reqwest::Url::parse(&v2_endpoint).map(|url| {
trace!("GET {:?}", url);
self.build_reqwest(Method::GET, url)
})?;
let response = request.send().await?;
let b = match (response.status(), response.headers().get(api_header)) {
(StatusCode::OK, Some(x)) => Ok((x == api_version, true)),
(StatusCode::UNAUTHORIZED, Some(x)) => Ok((x == api_version, false)),
(s, v) => {
trace!("Got unexpected status {}, header version {:?}", s, v);
return Err(crate::Error::UnexpectedHttpStatus(s));
}
};
b
}
fn build_reqwest(&self, method: Method, url: Url) -> reqwest::RequestBuilder {
let mut builder = self.client.request(method, url);
if let Some(auth) = &self.auth {
builder = auth.add_auth_headers(builder);
};
if let Some(ua) = &self.user_agent {
builder = builder.header(reqwest::header::USER_AGENT, ua.as_str());
};
builder
}
}
#[derive(Debug, Default, Deserialize, Serialize)]
struct ApiError {
code: String,
message: String,
detail: String,
}
#[derive(Debug, Default, Deserialize, Serialize)]
struct Errors {
errors: Vec<ApiError>,
}