diff options
Diffstat (limited to 'src/image.rs')
-rw-r--r-- | src/image.rs | 559 |
1 files changed, 556 insertions, 3 deletions
diff --git a/src/image.rs b/src/image.rs index 3b3bc10..fd78e6d 100644 --- a/src/image.rs +++ b/src/image.rs @@ -2,15 +2,15 @@ //! //! API Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Image> -use std::{io::Read, iter}; +use std::{collections::HashMap, io::Read, iter}; use futures_util::{stream::Stream, TryFutureExt, TryStreamExt}; use hyper::Body; +use serde::Serialize; use serde_json::Value; use url::form_urlencoded; use crate::{ - builder::{BuildOptions, ImageListOptions, PullOptions, TagOptions}, errors::Result, rep::{History, Image as ImageRep, ImageDetails, SearchResult, Status}, tarball, @@ -18,7 +18,6 @@ use crate::{ }; use crate::Docker; - /// Interface for accessing and manipulating a named docker image pub struct Image<'docker> { docker: &'docker Docker, @@ -227,3 +226,557 @@ impl<'docker> Images<'docker> { ) } } + +#[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 { + /// return a new instance with token authentication + pub fn token<S>(token: S) -> RegistryAuth + where + S: Into<String>, + { + RegistryAuth::Token { + identity_token: token.into(), + } + } + + /// return a new instance of a builder for authentication + pub fn builder() -> RegistryAuthBuilder { + RegistryAuthBuilder::default() + } + + /// serialize authentication as JSON in base64 + pub fn serialize(&self) -> String { + serde_json::to_string(self) + .map(|c| base64::encode_config(&c, base64::URL_SAFE)) + .unwrap() + } +} + +#[derive(Default)] +pub struct RegistryAuthBuilder { + username: Option<String>, + password: Option<String>, + email: Option<String>, + server_address: Option<String>, +} + +impl RegistryAuthBuilder { + pub fn username<I>( + &mut self, + username: I, + ) -> &mut Self + where + I: Into<String>, + { + self.username = Some(username.into()); + self + } + + pub fn password<I>( + &mut self, + password: I, + ) -> &mut Self + where + I: Into<String>, + { + self.password = Some(password.into()); + self + } + + pub fn email<I>( + &mut self, + email: I, + ) -> &mut Self + where + I: Into<String>, + { + self.email = Some(email.into()); + self + } + + pub fn server_address<I>( + &mut self, + server_address: I, + ) -> &mut Self + where + I: Into<String>, + { + self.server_address = Some(server_address.into()); + self + } + + pub fn build(&self) -> RegistryAuth { + RegistryAuth::Password { + username: self.username.clone().unwrap_or_else(String::new), + password: self.password.clone().unwrap_or_else(String::new), + email: self.email.clone(), + server_address: self.server_address.clone(), + } + } +} + +#[derive(Default, Debug)] +pub struct TagOptions { + pub params: HashMap<&'static str, String>, +} + +impl TagOptions { + /// return a new instance of a builder for options + pub fn builder() -> TagOptionsBuilder { + TagOptionsBuilder::default() + } + + /// serialize options as a string. returns None if no options are defined + pub fn serialize(&self) -> Option<String> { + if self.params.is_empty() { + None + } else { + Some( + form_urlencoded::Serializer::new(String::new()) + .extend_pairs(&self.params) + .finish(), + ) + } + } +} + +#[derive(Default)] +pub struct TagOptionsBuilder { + params: HashMap<&'static str, String>, +} + +impl TagOptionsBuilder { + pub fn repo<R>( + &mut self, + r: R, + ) -> &mut Self + where + R: Into<String>, + { + self.params.insert("repo", r.into()); + self + } + + pub fn tag<T>( + &mut self, + t: T, + ) -> &mut Self + where + T: Into<String>, + { + self.params.insert("tag", t.into()); + self + } + + pub fn build(&self) -> TagOptions { + TagOptions { + params: self.params.clone(), + } + } +} + +#[derive(Default, Debug)] +pub struct PullOptions { + auth: Option<RegistryAuth>, + params: HashMap<&'static str, String>, +} + +impl PullOptions { + /// return a new instance of a builder for options + pub fn builder() -> PullOptionsBuilder { + PullOptionsBuilder::default() + } + + /// serialize options as a string. returns None if no options are defined + pub fn serialize(&self) -> Option<String> { + if self.params.is_empty() { + None + } else { + Some( + form_urlencoded::Serializer::new(String::new()) + .extend_pairs(&self.params) + .finish(), + ) + } + } + + pub(crate) fn auth_header(&self) -> Option<String> { + self.auth.clone().map(|a| a.serialize()) + } +} + +#[derive(Default)] +pub struct PullOptionsBuilder { + auth: Option<RegistryAuth>, + params: HashMap<&'static str, String>, +} + +impl PullOptionsBuilder { + /// Name of the image to pull. The name may include a tag or digest. + /// This parameter may only be used when pulling an image. + /// If an untagged value is provided and no `tag` is provided, _all_ + /// tags will be pulled + /// The pull is cancelled if the HTTP connection is closed. + pub fn image<I>( + &mut self, + img: I, + ) -> &mut Self + where + I: Into<String>, + { + self.params.insert("fromImage", img.into()); + self + } + + pub fn src<S>( + &mut self, + s: S, + ) -> &mut Self + where + S: Into<String>, + { + self.params.insert("fromSrc", s.into()); + self + } + + /// Repository name given to an image when it is imported. The repo may include a tag. + /// This parameter may only be used when importing an image. + pub fn repo<R>( + &mut self, + r: R, + ) -> &mut Self + where + R: Into<String>, + { + self.params.insert("repo", r.into()); + self + } + + /// Tag or digest. If empty when pulling an image, + /// this causes all tags for the given image to be pulled. + pub fn tag<T>( + &mut self, + t: T, + ) -> &mut Self + where + T: Into<String>, + { + self.params.insert("tag", t.into()); + self + } + + pub fn auth( + &mut self, + auth: RegistryAuth, + ) -> &mut Self { + self.auth = Some(auth); + self + } + + pub fn build(&mut self) -> PullOptions { + PullOptions { + auth: self.auth.take(), + params: self.params.clone(), + } + } +} + +#[derive(Default, Debug)] +pub struct BuildOptions { + pub path: String, + params: HashMap<&'static str, String>, +} + +impl BuildOptions { + /// return a new instance of a builder for options + /// path is expected to be a file path to a directory containing a Dockerfile + /// describing how to build a Docker image + pub fn builder<S>(path: S) -> BuildOptionsBuilder + where + S: Into<String>, + { + BuildOptionsBuilder::new(path) + } + + /// serialize options as a string. returns None if no options are defined + pub fn serialize(&self) -> Option<String> { + if self.params.is_empty() { + None + } else { + Some( + form_urlencoded::Serializer::new(String::new()) + .extend_pairs(&self.params) + .finish(), + ) + } + } +} + +#[derive(Default)] +pub struct BuildOptionsBuilder { + path: String, + params: HashMap<&'static str, String>, +} + +impl BuildOptionsBuilder { + /// path is expected to be a file path to a directory containing a Dockerfile + /// describing how to build a Docker image + pub(crate) fn new<S>(path: S) -> Self + where + S: Into<String>, + { + BuildOptionsBuilder { + path: path.into(), + ..Default::default() + } + } + + /// set the name of the docker file. defaults to "DockerFile" + pub fn dockerfile<P>( + &mut self, + path: P, + ) -> &mut Self + where + P: Into<String>, + { + self.params.insert("dockerfile", path.into()); + self + } + + /// tag this image with a name after building it + pub fn tag<T>( + &mut self, + t: T, + ) -> &mut Self + where + T: Into<String>, + { + self.params.insert("t", t.into()); + self + } + + pub fn remote<R>( + &mut self, + r: R, + ) -> &mut Self + where + R: Into<String>, + { + self.params.insert("remote", r.into()); + self + } + + /// don't use the image cache when building image + pub fn nocache( + &mut self, + nc: bool, + ) -> &mut Self { + self.params.insert("nocache", nc.to_string()); + self + } + + pub fn rm( + &mut self, + r: bool, + ) -> &mut Self { + self.params.insert("rm", r.to_string()); + self + } + + pub fn forcerm( + &mut self, + fr: bool, + ) -> &mut Self { + self.params.insert("forcerm", fr.to_string()); + self + } + + /// `bridge`, `host`, `none`, `container:<name|id>`, or a custom network name. + pub fn network_mode<T>( + &mut self, + t: T, + ) -> &mut Self + where + T: Into<String>, + { + self.params.insert("networkmode", t.into()); + self + } + + pub fn memory( + &mut self, + memory: u64, + ) -> &mut Self { + self.params.insert("memory", memory.to_string()); + self + } + + pub fn cpu_shares( + &mut self, + cpu_shares: u32, + ) -> &mut Self { + self.params.insert("cpushares", cpu_shares.to_string()); + self + } + + // todo: memswap + // todo: cpusetcpus + // todo: cpuperiod + // todo: cpuquota + // todo: buildargs + + pub fn build(&self) -> BuildOptions { + BuildOptions { + path: self.path.clone(), + params: self.params.clone(), + } + } +} + +/// Filter options for image listings +pub enum ImageFilter { + Dangling, + LabelName(String), + Label(String, String), +} + +/// Options for filtering image list results +#[derive(Default, Debug)] +pub struct ImageListOptions { + params: HashMap<&'static str, String>, +} + +impl ImageListOptions { + pub fn builder() -> ImageListOptionsBuilder { + ImageListOptionsBuilder::default() + } + pub fn serialize(&self) -> Option<String> { + if self.params.is_empty() { + None + } else { + Some( + form_urlencoded::Serializer::new(String::new()) + .extend_pairs(&self.params) + .finish(), + ) + } + } +} + +/// Builder interface for `ImageListOptions` +#[derive(Default)] +pub struct ImageListOptionsBuilder { + params: HashMap<&'static str, String>, +} + +impl ImageListOptionsBuilder { + pub fn digests( + &mut self, + d: bool, + ) -> &mut Self { + self.params.insert("digests", d.to_string()); + self + } + + pub fn all(&mut self) -> &mut Self { + self.params.insert("all", "true".to_owned()); + self + } + + pub fn filter_name( + &mut self, + name: &str, + ) -> &mut Self { + self.params.insert("filter", name.to_owned()); + self + } + + pub fn filter( + &mut self, + filters: Vec<ImageFilter>, + ) -> &mut Self { + let mut param = HashMap::new(); + for f in filters { + match f { + ImageFilter::Dangling => param.insert("dangling", vec![true.to_string()]), + ImageFilter::LabelName(n) => param.insert("label", vec![n]), + ImageFilter::Label(n, v) => param.insert("label", vec![format!("{}={}", n, v)]), + }; + } + // structure is a a json encoded object mapping string keys to a list + // of string values + self.params + .insert("filters", serde_json::to_string(¶m).unwrap()); + self + } + + pub fn build(&self) -> ImageListOptions { + ImageListOptions { + params: self.params.clone(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test registry auth with token + #[test] + fn registry_auth_token() { + let options = RegistryAuth::token("abc"); + assert_eq!( + base64::encode(r#"{"identitytoken":"abc"}"#), + options.serialize() + ); + } + + /// Test registry auth with username and password + #[test] + fn registry_auth_password_simple() { + let options = RegistryAuth::builder() + .username("user_abc") + .password("password_abc") + .build(); + assert_eq!( + base64::encode(r#"{"username":"user_abc","password":"password_abc"}"#), + options.serialize() + ); + } + + /// Test registry auth with all fields + #[test] + fn registry_auth_password_all() { + let options = 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"}"# + ), + options.serialize() + ); + } +} |