use crate::{api::Filter, util::url::encoded_pairs};
use std::{
collections::HashMap,
path::{Path, PathBuf},
string::ToString,
};
use serde::Serialize;
#[derive(Clone, Serialize, Debug)]
#[serde(untagged)]
pub enum RegistryAuth {
Password {
username: String,
password: String,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
#[serde(rename = "serveraddress")]
#[serde(skip_serializing_if = "Option::is_none")]
server_address: Option<String>,
},
Token {
#[serde(rename = "identitytoken")]
identity_token: String,
},
}
impl RegistryAuth {
pub fn token<S>(token: S) -> RegistryAuth
where
S: Into<String>,
{
RegistryAuth::Token {
identity_token: token.into(),
}
}
pub fn builder() -> RegistryAuthBuilder {
RegistryAuthBuilder::default()
}
pub fn serialize(&self) -> String {
serde_json::to_string(self)
.map(|c| base64::encode_config(&c, base64::URL_SAFE))
.unwrap_or_default()
}
}
#[derive(Default)]
pub struct RegistryAuthBuilder {
username: Option<String>,
password: Option<String>,
email: Option<String>,
server_address: Option<String>,
}
impl RegistryAuthBuilder {
pub fn username<U>(mut self, username: U) -> Self
where
U: Into<String>,
{
self.username = Some(username.into());
self
}
pub fn password<P>(mut self, password: P) -> Self
where
P: Into<String>,
{
self.password = Some(password.into());
self
}
pub fn email<E>(mut self, email: E) -> Self
where
E: Into<String>,
{
self.email = Some(email.into());
self
}
pub fn server_address<A>(mut self, server_address: A) -> Self
where
A: Into<String>,
{
self.server_address = Some(server_address.into());
self
}
pub fn build(&self) -> RegistryAuth {
RegistryAuth::Password {
username: self.username.clone().unwrap_or_default(),
password: self.password.clone().unwrap_or_default(),
email: self.email.clone(),
server_address: self.server_address.clone(),
}
}
}
impl_opts_builder!(url => Tag);
impl TagOptsBuilder {
impl_url_str_field!(repo: R => "repo");
impl_url_str_field!(tag: T => "tag");
}
#[derive(Default, Debug)]
pub struct PullOpts {
auth: Option<RegistryAuth>,
params: HashMap<&'static str, serde_json::Value>,
}
impl PullOpts {
pub fn builder() -> PullOptsBuilder {
PullOptsBuilder::default()
}
pub fn serialize(&self) -> Option<String> {
if self.params.is_empty() {
None
} else {
Some(encoded_pairs(
self.params
.iter()
.map(|(k, v)| (k, v.as_str().unwrap_or_default())),
))
}
}
pub(crate) fn auth_header(&self) -> Option<String> {
self.auth.clone().map(|a| a.serialize())
}
}
pub struct PullOptsBuilder {
auth: Option<RegistryAuth>,
params: HashMap<&'static str, serde_json::Value>,
}
impl Default for PullOptsBuilder {
fn default() -> Self {
let mut params = HashMap::new();
params.insert("tag", serde_json::Value::String("latest".into()));
PullOptsBuilder { auth: None, params }
}
}
impl PullOptsBuilder {
impl_str_field!(
image: I => "fromImage");
impl_str_field!(src: S => "fromSrc");
impl_str_field!(
repo: S => "repo");
impl_str_field!(
tag: T => "tag");
pub fn auth(mut self, auth: RegistryAuth) -> Self {
self.auth = Some(auth);
self
}
pub fn build(self) -> PullOpts {
PullOpts {
auth: self.auth,
params: self.params,
}
}
}
#[derive(Default, Debug)]
pub struct BuildOpts {
pub path: PathBuf,
params: HashMap<&'static str, String>,
}
impl BuildOpts {
pub fn builder<P>(path: P) -> BuildOptsBuilder
where
P: AsRef<Path>,
{
BuildOptsBuilder::new(path)
}
pub fn serialize(&self) -> Option<String> {
if self.params.is_empty() {
None
} else {
Some(encoded_pairs(&self.params))
}
}
}
#[derive(Default)]
pub struct BuildOptsBuilder {
path: PathBuf,
params: HashMap<&'static str, String>,
}
impl BuildOptsBuilder {
pub(crate) fn new<P>(path: P) -> Self
where
P: AsRef<Path>,
{
BuildOptsBuilder {
path: path.as_ref().to_path_buf(),
..Default::default()
}
}
impl_url_str_field!(
dockerfile: P => "dockerfile"
);
impl_url_str_field!(
tag: T => "t"
);
impl_url_str_field!(
extra_hosts: H => "extrahosts"
);
impl_url_str_field!(remote: R => "remote");
impl_url_bool_field!(
quiet => "q"
);
impl_url_bool_field!(
nocahe => "nocache"
);
impl_url_str_field!(
pull: I => "pull"
);
impl_url_bool_field!(rm => "rm");
impl_url_bool_field!(forcerm => "forcerm");
impl_url_field!(
memory: usize => "memory"
);
impl_url_field!(
memswap: usize => "memswap"
);
impl_url_field!(
cpu_shares: usize => "cpushares"
);
impl_url_str_field!(
cpu_set_cpus: C => "cpusetcpus"
);
impl_url_field!(
cpu_period: usize => "cpuperiod"
);
impl_url_field!(
cpu_quota: usize => "cpuquota"
);
impl_url_field!(
shm_size: usize => "shmsize"
);
impl_url_bool_field!(
squash => "squash"
);
impl_url_str_field!(
network_mode: M => "networkmode"
);
impl_url_str_field!(
platform: P => "platform"
);
impl_url_str_field!(
target: T => "target"
);
impl_url_str_field!(
outputs: C => "outputs"
);
impl_map_field!(url
labels: L => "labels"
);
pub fn build(&self) -> BuildOpts {
BuildOpts {
path: self.path.clone(),
params: self.params.clone(),
}
}
}
pub enum ImageName {
Tag { image: String, tag: Option<String> },
Id(String),
Digest { image: String, digest: String },
}
impl ToString for ImageName {
fn to_string(&self) -> String {
match &self {
ImageName::Tag { image, tag } => match tag {
Some(tag) => format!("{}:{}", image, tag),
None => image.to_owned(),
},
ImageName::Id(id) => id.to_owned(),
ImageName::Digest { image, digest } => format!("{}@{}", image, digest),
}
}
}
impl ImageName {
pub fn tag<I, T>(image: I, tag: Option<T>) -> Self
where
I: Into<String>,
T: Into<String>,
{
Self::Tag {
image: image.into(),
tag: tag.map(|t| t.into()),
}
}
pub fn id<I>(id: I) -> Self
where
I: Into<String>,
{
Self::Id(id.into())
}
pub fn digest<I, D>(image: I, digest: D) -> Self
where
I: Into<String>,
D: Into<String>,
{
Self::Digest {
image: image.into(),
digest: digest.into(),
}
}
}
pub enum ImageFilter {
Before(ImageName),
Dangling,
LabelKey(String),
Label(String, String),
Since(ImageName),
}
impl Filter for ImageFilter {
fn query_key_val(&self) -> (&'static str, String) {
use ImageFilter::*;
match &self {
Before(name) => ("before", name.to_string()),
Dangling => ("dangling", true.to_string()),
LabelKey(n) => ("label", n.to_owned()),
Label(n, v) => ("label", format!("{}={}", n, v)),
Since(name) => ("since", name.to_string()),
}
}
}
impl_opts_builder!(url => ImageList);
impl ImageListOptsBuilder {
impl_url_bool_field!(
all => "all"
);
impl_url_bool_field!(
digests => "digests"
);
impl_filter_func!(
ImageFilter
);
}
impl_opts_builder!(url => RmImage);
impl RmImageOptsBuilder {
impl_url_bool_field!(
force => "force"
);
impl_url_bool_field!(
noprune => "noprune"
);
}
impl_opts_builder!(url => ImagePrune);
pub enum ImagesPruneFilter {
Dangling(bool),
#[cfg(feature = "chrono")]
#[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
UntilDate(chrono::DateTime<chrono::Utc>),
Until(String),
LabelKey(String),
Label(String, String),
}
impl Filter for ImagesPruneFilter {
fn query_key_val(&self) -> (&'static str, String) {
use ImagesPruneFilter::*;
match &self {
Dangling(dangling) => ("dangling", dangling.to_string()),
Until(until) => ("until", until.to_owned()),
#[cfg(feature = "chrono")]
UntilDate(until) => ("until", until.timestamp().to_string()),
LabelKey(label) => ("label", label.to_owned()),
Label(key, val) => ("label", format!("{}={}", key, val)),
}
}
}
impl ImagePruneOptsBuilder {
impl_filter_func!(ImagesPruneFilter);
}
impl_opts_builder!(url => ClearCache);
pub enum CacheFilter {
Until(String),
Id(String),
Parent(String),
Type(String),
Description(String),
InUse,
Shared,
Private,
}
impl Filter for CacheFilter {
fn query_key_val(&self) -> (&'static str, String) {
use CacheFilter::*;
match &self {
Until(until) => ("until", until.to_owned()),
Id(id) => ("id", id.to_owned()),
Parent(parent) => ("parent", parent.to_owned()),
Type(type_) => ("type_", type_.to_owned()),
Description(description) => ("description", description.to_owned()),
InUse => ("inuse", "".to_owned()),
Shared => ("shared", "".to_owned()),
Private => ("private", "".to_owned()),
}
}
}
impl ClearCacheOptsBuilder {
impl_url_field!(
keep_storage: i64 => "keep-storage"
);
impl_url_bool_field!(
all => "all"
);
impl_filter_func!(
CacheFilter
);
}
pub struct ImagePushOpts {
auth: Option<RegistryAuth>,
params: HashMap<&'static str, String>,
}
impl ImagePushOpts {
pub fn builder() -> ImagePushOptsBuilder {
ImagePushOptsBuilder::default()
}
pub fn serialize(&self) -> Option<String> {
if self.params.is_empty() {
None
} else {
Some(encoded_pairs(self.params.iter()))
}
}
pub(crate) fn auth_header(&self) -> Option<String> {
self.auth.clone().map(|a| a.serialize())
}
}
pub struct ImagePushOptsBuilder {
auth: Option<RegistryAuth>,
params: HashMap<&'static str, String>,
}
impl Default for ImagePushOptsBuilder {
fn default() -> Self {
Self {
auth: None,
params: [("tag", "latest".into())].into(),
}
}
}
impl ImagePushOptsBuilder {
impl_url_str_field!(
tag: T => "tag"
);
pub fn auth(mut self, auth: RegistryAuth) -> Self {
self.auth = Some(auth);
self
}
pub fn build(self) -> ImagePushOpts {
ImagePushOpts {
auth: self.auth,
params: self.params,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn registry_auth_token() {
let opts = RegistryAuth::token("abc");
assert_eq!(
base64::encode(r#"{"identitytoken":"abc"}"#),
opts.serialize()
);
}
#[test]
fn registry_auth_password_simple() {
let opts = RegistryAuth::builder()
.username("user_abc")
.password("password_abc")
.build();
assert_eq!(
base64::encode(r#"{"username":"user_abc","password":"password_abc"}"#),
opts.serialize()
);
}
#[test]
fn registry_auth_password_all() {
let opts = RegistryAuth::builder()
.username("user_abc")
.password("password_abc")
.email("email_abc")
.server_address("https://example.org")
.build();
assert_eq!(
base64::encode(
r#"{"username":"user_abc","password":"password_abc","email":"email_abc","serveraddress":"https://example.org"}"#
),
opts.serialize()
);
}
}