Skip to main content

dkregistry/v2/
mod.rs

1//! Client library for Docker Registry API v2.
2//!
3//! This module provides a `Client` which can be used to list
4//! images and tags, to check for the presence of blobs (manifests,
5//! layers and other objects) by digest, and to retrieve them.
6//!
7//! ## Example
8//!
9//! ```rust,no_run
10//! # extern crate dkregistry;
11//! # extern crate tokio;
12//! # #[tokio::main]
13//! # async fn main() {
14//! # async fn run() -> dkregistry::errors::Result<()> {
15//! #
16//! use dkregistry::v2::Client;
17//!
18//! // Retrieve an image manifest.
19//! let dclient = Client::configure()
20//!                      .registry("quay.io")
21//!                      .build()?;
22//! let manifest = dclient.get_manifest("coreos/etcd", "v3.1.0").await?;
23//! #
24//! # Ok(())
25//! # };
26//! # run().await.unwrap();
27//! # }
28//! ```
29
30use 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/// A Client to make outgoing API requests to a registry.
53#[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    /// Ensure remote registry supports v2 API.
69    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    /// Check whether remote registry supports v2 API.
78    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    /// Check whether remote registry supports v2 API and `self` is authorized.
87    /// Authorized means to successfully GET the `/v2` endpoint on the remote registry.
88    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        // GET request to bare v2 endpoint.
93        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    /// Takes reqwest's async RequestBuilder and injects an authentication header if a token is present
114    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}