use std::{
collections::HashMap,
path::{Path, PathBuf},
string::ToString,
};
use containers_api::opts::{Filter, FilterItem};
use containers_api::url::encoded_pairs;
use containers_api::{
impl_filter_func, impl_map_field, impl_opts_builder, impl_str_field, impl_url_bool_field,
impl_url_field, impl_url_str_field,
};
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 => "repo");
impl_url_str_field!(tag => "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 => "fromImage");
impl_str_field!(src => "fromSrc");
impl_str_field!(
repo => "repo");
impl_str_field!(
tag => "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, Clone)]
pub struct ImageBuildOpts {
pub path: PathBuf,
params: HashMap<&'static str, String>,
}
impl ImageBuildOpts {
pub fn builder<P>(path: P) -> ImageBuildOptsBuilder
where
P: AsRef<Path>,
{
ImageBuildOptsBuilder::new(path)
}
pub fn serialize(&self) -> Option<String> {
if self.params.is_empty() {
None
} else {
Some(encoded_pairs(&self.params))
}
}
}
#[derive(Default)]
pub struct ImageBuildOptsBuilder {
path: PathBuf,
params: HashMap<&'static str, String>,
}
impl ImageBuildOptsBuilder {
pub(crate) fn new<P>(path: P) -> Self
where
P: AsRef<Path>,
{
ImageBuildOptsBuilder {
path: path.as_ref().to_path_buf(),
..Default::default()
}
}
impl_url_str_field!(
dockerfile => "dockerfile"
);
impl_url_str_field!(
tag => "t"
);
impl_url_str_field!(
extra_hosts => "extrahosts"
);
impl_url_str_field!(remote => "remote");
impl_url_bool_field!(
quiet => "q"
);
impl_url_bool_field!(
nocahe => "nocache"
);
impl_url_str_field!(
pull => "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 => "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 => "networkmode"
);
impl_url_str_field!(
platform => "platform"
);
impl_url_str_field!(
target => "target"
);
impl_url_str_field!(
outputs => "outputs"
);
impl_map_field!(url
labels => "labels"
);
pub fn build(&self) -> ImageBuildOpts {
ImageBuildOpts {
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),
Reference(String, Option<String>),
}
impl Filter for ImageFilter {
fn query_item(&self) -> FilterItem {
use ImageFilter::*;
match &self {
Before(name) => FilterItem::new("before", name.to_string()),
Dangling => FilterItem::new("dangling", true.to_string()),
LabelKey(n) => FilterItem::new("label", n.to_owned()),
Label(n, v) => FilterItem::new("label", format!("{n}={v}")),
Since(name) => FilterItem::new("since", name.to_string()),
Reference(image, tag) => FilterItem::new(
"reference",
format!(
"{}{}",
image,
tag.as_ref()
.map_or("".to_string(), |tag| format!(":{}", tag))
),
),
}
}
}
impl_opts_builder!(url => ImageList);
impl ImageListOptsBuilder {
impl_url_bool_field!(
all => "all"
);
impl_url_bool_field!(
digests => "digests"
);
impl_url_bool_field!(
shared_size => "shared-size"
);
impl_filter_func!(
ImageFilter
);
}
impl_opts_builder!(url => ImageRemove);
impl ImageRemoveOptsBuilder {
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_item(&self) -> FilterItem {
use ImagesPruneFilter::*;
match &self {
Dangling(dangling) => FilterItem::new("dangling", dangling.to_string()),
Until(until) => FilterItem::new("until", until.to_owned()),
#[cfg(feature = "chrono")]
UntilDate(until) => FilterItem::new("until", until.timestamp().to_string()),
LabelKey(label) => FilterItem::new("label", label.to_owned()),
Label(key, val) => FilterItem::new("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_item(&self) -> FilterItem {
use CacheFilter::*;
match &self {
Until(until) => FilterItem::new("until", until.to_owned()),
Id(id) => FilterItem::new("id", id.to_owned()),
Parent(parent) => FilterItem::new("parent", parent.to_owned()),
Type(type_) => FilterItem::new("type_", type_.to_owned()),
Description(description) => FilterItem::new("description", description.to_owned()),
InUse => FilterItem::new("inuse", "".to_owned()),
Shared => FilterItem::new("shared", "".to_owned()),
Private => FilterItem::new("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 => "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()
);
}
#[test]
fn test_image_filter_reference() {
let opts = ImageListOpts::builder()
.filter(vec![ImageFilter::Reference("image".to_string(), None)])
.build();
let serialized = opts.serialize();
assert!(serialized.is_some());
assert_eq!(
"filters=%7B%22reference%22%3A%5B%22image%22%5D%7D".to_string(),
serialized.unwrap()
);
let opts = ImageListOpts::builder()
.filter(vec![ImageFilter::Reference(
"image".to_string(),
Some("tag".to_string()),
)])
.build();
let serialized = opts.serialize();
assert!(serialized.is_some());
assert_eq!(
"filters=%7B%22reference%22%3A%5B%22image%3Atag%22%5D%7D".to_string(),
serialized.unwrap()
);
}
}