Skip to main content

docker_registry/
lib.rs

1//! A pure-Rust asynchronous library for Docker Registry API.
2//!
3//! This library provides support for asynchronous interaction with
4//! container registries conformant to the Docker Registry HTTP API V2.
5//!
6//! ## Example
7//!
8//! ```rust,no_run
9//! # use tokio;
10//!
11//! # #[tokio::main]
12//! # async fn main() {
13//! # async fn run() -> docker_registry::errors::Result<()> {
14//! #
15//! use docker_registry::v2::Client;
16//!
17//! // Check whether a registry supports API v2.
18//! let host = "quay.io";
19//! let client = Client::configure()
20//!   .insecure_registry(false)
21//!   .registry(host)
22//!   .build()?;
23//! match client.is_v2_supported().await? {
24//!   false => println!("{} does NOT support v2", host),
25//!   true => println!("{} supports v2", host),
26//! };
27//! #
28//! # Ok(())
29//! # };
30//! # run().await.unwrap();
31//! # }
32//! ```
33
34#![deny(missing_debug_implementations)]
35
36use log::trace;
37use serde::{Deserialize, Serialize};
38
39pub mod errors;
40pub mod mediatypes;
41pub mod reference;
42pub mod render;
43pub mod v2;
44
45use std::{collections::HashMap, io::Read};
46
47use base64::prelude::*;
48use errors::{Error, Result};
49
50/// Default User-Agent client identity.
51pub static USER_AGENT: &str = concat!("clowdhaus/docker-registry/", env!("CARGO_PKG_VERSION"));
52
53/// Get registry credentials from a JSON config reader.
54///
55/// This is a convenience decoder for docker-client credentials
56/// typically stored under `~/.docker/config.json`.
57pub fn get_credentials<T: Read>(reader: T, index: &str) -> Result<(Option<String>, Option<String>)> {
58  let map: Auths = serde_json::from_reader(reader)?;
59  let real_index = match index {
60    // docker.io has some special casing in config.json
61    "docker.io" | "registry-1.docker.io" => "https://index.docker.io/v1/",
62    other => other,
63  };
64  let auth = match map.auths.get(real_index) {
65    Some(x) => BASE64_STANDARD.decode(x.auth.as_str())?,
66    None => return Err(Error::AuthInfoMissing(real_index.to_string())),
67  };
68  let s = String::from_utf8(auth)?;
69  let creds: Vec<&str> = s.splitn(2, ':').collect();
70  let up = match (creds.first(), creds.get(1)) {
71    (Some(&""), Some(p)) => (None, Some(p.to_string())),
72    (Some(u), Some(&"")) => (Some(u.to_string()), None),
73    (Some(u), Some(p)) => (Some(u.to_string()), Some(p.to_string())),
74    (_, _) => (None, None),
75  };
76  trace!("Found credentials for user={:?} on {}", up.0, index);
77  Ok(up)
78}
79
80#[derive(Debug, Deserialize, Serialize)]
81struct Auths {
82  auths: HashMap<String, AuthObj>,
83}
84
85#[derive(Debug, Default, Deserialize, Serialize)]
86struct AuthObj {
87  auth: String,
88}
89
90#[cfg(test)]
91mod tests {
92  use super::*;
93
94  #[test]
95  fn test_get_credentials_valid() {
96    let config = r#"{"auths":{"https://index.docker.io/v1/":{"auth":"dXNlcjpwYXNz"}}}"#;
97    let (user, pass) = get_credentials(config.as_bytes(), "docker.io").unwrap();
98    assert_eq!(user, Some("user".to_string()));
99    assert_eq!(pass, Some("pass".to_string()));
100  }
101
102  #[test]
103  fn test_get_credentials_registry1_docker_io() {
104    let config = r#"{"auths":{"https://index.docker.io/v1/":{"auth":"dXNlcjpwYXNz"}}}"#;
105    let (user, pass) = get_credentials(config.as_bytes(), "registry-1.docker.io").unwrap();
106    assert_eq!(user, Some("user".to_string()));
107    assert_eq!(pass, Some("pass".to_string()));
108  }
109
110  #[test]
111  fn test_get_credentials_custom_registry() {
112    // base64("admin:secret") = "YWRtaW46c2VjcmV0"
113    let config = r#"{"auths":{"myregistry.example.com":{"auth":"YWRtaW46c2VjcmV0"}}}"#;
114    let (user, pass) = get_credentials(config.as_bytes(), "myregistry.example.com").unwrap();
115    assert_eq!(user, Some("admin".to_string()));
116    assert_eq!(pass, Some("secret".to_string()));
117  }
118
119  #[test]
120  fn test_get_credentials_missing_registry() {
121    let config = r#"{"auths":{"other.io":{"auth":"dXNlcjpwYXNz"}}}"#;
122    let result = get_credentials(config.as_bytes(), "missing.io");
123    assert!(result.is_err());
124    assert!(matches!(result.unwrap_err(), Error::AuthInfoMissing(_)));
125  }
126
127  #[test]
128  fn test_get_credentials_empty_user() {
129    // base64(":password") = "OnBhc3N3b3Jk"
130    let config = r#"{"auths":{"reg.io":{"auth":"OnBhc3N3b3Jk"}}}"#;
131    let (user, pass) = get_credentials(config.as_bytes(), "reg.io").unwrap();
132    assert_eq!(user, None);
133    assert_eq!(pass, Some("password".to_string()));
134  }
135
136  #[test]
137  fn test_get_credentials_empty_password() {
138    // base64("user:") = "dXNlcjo="
139    let config = r#"{"auths":{"reg.io":{"auth":"dXNlcjo="}}}"#;
140    let (user, pass) = get_credentials(config.as_bytes(), "reg.io").unwrap();
141    assert_eq!(user, Some("user".to_string()));
142    assert_eq!(pass, None);
143  }
144
145  #[test]
146  fn test_get_credentials_invalid_json() {
147    let config = r#"not json"#;
148    let result = get_credentials(config.as_bytes(), "reg.io");
149    assert!(result.is_err());
150  }
151
152  #[test]
153  fn test_get_credentials_invalid_base64() {
154    let config = r#"{"auths":{"reg.io":{"auth":"!!!invalid!!!"}}}"#;
155    let result = get_credentials(config.as_bytes(), "reg.io");
156    assert!(result.is_err());
157  }
158}