From a871f1df049356d035da196676125f3585912f49 Mon Sep 17 00:00:00 2001 From: "Eli W. Hunter" Date: Mon, 8 Feb 2021 19:26:21 -0500 Subject: Split up builder.rs --- src/builder.rs | 2249 ------------------------------------------------------ src/container.rs | 926 +++++++++++++++++++++- src/docker.rs | 147 +++- src/exec.rs | 175 ++++- src/image.rs | 559 +++++++++++++- src/lib.rs | 82 +- src/network.rs | 172 ++++- src/service.rs | 233 +++++- src/volume.rs | 79 +- 9 files changed, 2337 insertions(+), 2285 deletions(-) delete mode 100644 src/builder.rs (limited to 'src') diff --git a/src/builder.rs b/src/builder.rs deleted file mode 100644 index 01a2d76..0000000 --- a/src/builder.rs +++ /dev/null @@ -1,2249 +0,0 @@ -//! Interfaces for building various structures - -use crate::{ - errors::Error, - rep::{EndpointSpec, Mode, NetworkAttachmentConfig, RollbackConfig, TaskSpec, UpdateConfig}, - Result, -}; -use serde::Serialize; -use serde_json::{self, json, map::Map, Value}; -use std::{ - cmp::Eq, - collections::{BTreeMap, HashMap}, - hash::Hash, - iter::{IntoIterator, Iterator, Peekable}, - time::Duration, -}; -use url::form_urlencoded; - -#[derive(Clone, Serialize, Debug)] -#[serde(untagged)] -pub enum RegistryAuth { - Password { - username: String, - password: String, - - #[serde(skip_serializing_if = "Option::is_none")] - email: Option, - - #[serde(rename = "serveraddress")] - #[serde(skip_serializing_if = "Option::is_none")] - server_address: Option, - }, - Token { - #[serde(rename = "identitytoken")] - identity_token: String, - }, -} - -impl RegistryAuth { - /// return a new instance with token authentication - pub fn token(token: S) -> RegistryAuth - where - S: Into, - { - 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, - password: Option, - email: Option, - server_address: Option, -} - -impl RegistryAuthBuilder { - pub fn username( - &mut self, - username: I, - ) -> &mut Self - where - I: Into, - { - self.username = Some(username.into()); - self - } - - pub fn password( - &mut self, - password: I, - ) -> &mut Self - where - I: Into, - { - self.password = Some(password.into()); - self - } - - pub fn email( - &mut self, - email: I, - ) -> &mut Self - where - I: Into, - { - self.email = Some(email.into()); - self - } - - pub fn server_address( - &mut self, - server_address: I, - ) -> &mut Self - where - I: Into, - { - 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 { - 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( - &mut self, - r: R, - ) -> &mut Self - where - R: Into, - { - self.params.insert("repo", r.into()); - self - } - - pub fn tag( - &mut self, - t: T, - ) -> &mut Self - where - T: Into, - { - 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, - 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 { - 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 { - self.auth.clone().map(|a| a.serialize()) - } -} - -#[derive(Default)] -pub struct PullOptionsBuilder { - auth: Option, - 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( - &mut self, - img: I, - ) -> &mut Self - where - I: Into, - { - self.params.insert("fromImage", img.into()); - self - } - - pub fn src( - &mut self, - s: S, - ) -> &mut Self - where - S: Into, - { - 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( - &mut self, - r: R, - ) -> &mut Self - where - R: Into, - { - 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( - &mut self, - t: T, - ) -> &mut Self - where - T: Into, - { - 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(path: S) -> BuildOptionsBuilder - where - S: Into, - { - BuildOptionsBuilder::new(path) - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - 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(path: S) -> Self - where - S: Into, - { - BuildOptionsBuilder { - path: path.into(), - ..Default::default() - } - } - - /// set the name of the docker file. defaults to "DockerFile" - pub fn dockerfile

( - &mut self, - path: P, - ) -> &mut Self - where - P: Into, - { - self.params.insert("dockerfile", path.into()); - self - } - - /// tag this image with a name after building it - pub fn tag( - &mut self, - t: T, - ) -> &mut Self - where - T: Into, - { - self.params.insert("t", t.into()); - self - } - - pub fn remote( - &mut self, - r: R, - ) -> &mut Self - where - R: Into, - { - 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:`, or a custom network name. - pub fn network_mode( - &mut self, - t: T, - ) -> &mut Self - where - T: Into, - { - 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(), - } - } -} - -/// Options for filtering container list results -#[derive(Default, Debug)] -pub struct ContainerListOptions { - params: HashMap<&'static str, String>, -} - -impl ContainerListOptions { - /// return a new instance of a builder for options - pub fn builder() -> ContainerListOptionsBuilder { - ContainerListOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - if self.params.is_empty() { - None - } else { - Some( - form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&self.params) - .finish(), - ) - } - } -} - -/// Filter options for container listings -pub enum ContainerFilter { - ExitCode(u64), - Status(String), - LabelName(String), - Label(String, String), -} - -/// Builder interface for `ContainerListOptions` -#[derive(Default)] -pub struct ContainerListOptionsBuilder { - params: HashMap<&'static str, String>, -} - -impl ContainerListOptionsBuilder { - pub fn filter( - &mut self, - filters: Vec, - ) -> &mut Self { - let mut param = HashMap::new(); - for f in filters { - match f { - ContainerFilter::ExitCode(c) => param.insert("exit", vec![c.to_string()]), - ContainerFilter::Status(s) => param.insert("status", vec![s]), - ContainerFilter::LabelName(n) => param.insert("label", vec![n]), - ContainerFilter::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 all(&mut self) -> &mut Self { - self.params.insert("all", "true".to_owned()); - self - } - - pub fn since( - &mut self, - since: &str, - ) -> &mut Self { - self.params.insert("since", since.to_owned()); - self - } - - pub fn before( - &mut self, - before: &str, - ) -> &mut Self { - self.params.insert("before", before.to_owned()); - self - } - - pub fn sized(&mut self) -> &mut Self { - self.params.insert("size", "true".to_owned()); - self - } - - pub fn build(&self) -> ContainerListOptions { - ContainerListOptions { - params: self.params.clone(), - } - } -} - -/// Interface for building a new docker container from an existing image -#[derive(Serialize, Debug)] -pub struct ContainerOptions { - pub name: Option, - params: HashMap<&'static str, Value>, -} - -/// Function to insert a JSON value into a tree where the desired -/// location of the value is given as a path of JSON keys. -fn insert<'a, I, V>( - key_path: &mut Peekable, - value: &V, - parent_node: &mut Value, -) where - V: Serialize, - I: Iterator, -{ - let local_key = key_path.next().unwrap(); - - if key_path.peek().is_some() { - let node = parent_node - .as_object_mut() - .unwrap() - .entry(local_key.to_string()) - .or_insert(Value::Object(Map::new())); - - insert(key_path, value, node); - } else { - parent_node - .as_object_mut() - .unwrap() - .insert(local_key.to_string(), serde_json::to_value(value).unwrap()); - } -} - -impl ContainerOptions { - /// return a new instance of a builder for options - pub fn builder(name: &str) -> ContainerOptionsBuilder { - ContainerOptionsBuilder::new(name) - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - serde_json::to_string(&self.to_json()).map_err(Error::from) - } - - fn to_json(&self) -> Value { - let mut body_members = Map::new(); - // The HostConfig element gets initialized to an empty object, - // for backward compatibility. - body_members.insert("HostConfig".to_string(), Value::Object(Map::new())); - let mut body = Value::Object(body_members); - self.parse_from(&self.params, &mut body); - body - } - - pub fn parse_from<'a, K, V>( - &self, - params: &'a HashMap, - body: &mut Value, - ) where - &'a HashMap: IntoIterator, - K: ToString + Eq + Hash, - V: Serialize, - { - for (k, v) in params.iter() { - let key_string = k.to_string(); - insert(&mut key_string.split('.').peekable(), v, body) - } - } -} - -#[derive(Default)] -pub struct ContainerOptionsBuilder { - name: Option, - params: HashMap<&'static str, Value>, -} - -impl ContainerOptionsBuilder { - pub(crate) fn new(image: &str) -> Self { - let mut params = HashMap::new(); - - params.insert("Image", Value::String(image.to_owned())); - ContainerOptionsBuilder { name: None, params } - } - - pub fn name( - &mut self, - name: &str, - ) -> &mut Self { - self.name = Some(name.to_owned()); - self - } - - /// Specify the working dir (corresponds to the `-w` docker cli argument) - pub fn working_dir( - &mut self, - working_dir: &str, - ) -> &mut Self { - self.params.insert("WorkingDir", json!(working_dir)); - self - } - - /// Specify any bind mounts, taking the form of `/some/host/path:/some/container/path` - pub fn volumes( - &mut self, - volumes: Vec<&str>, - ) -> &mut Self { - self.params.insert("HostConfig.Binds", json!(volumes)); - self - } - - /// enable all exposed ports on the container to be mapped to random, available, ports on the host - pub fn publish_all_ports(&mut self) -> &mut Self { - self.params - .insert("HostConfig.PublishAllPorts", json!(true)); - self - } - - pub fn expose( - &mut self, - srcport: u32, - protocol: &str, - hostport: u32, - ) -> &mut Self { - let mut exposedport: HashMap = HashMap::new(); - exposedport.insert("HostPort".to_string(), hostport.to_string()); - - /* The idea here is to go thought the 'old' port binds - * and to apply them to the local 'port_bindings' variable, - * add the bind we want and replace the 'old' value */ - let mut port_bindings: HashMap = HashMap::new(); - for (key, val) in self - .params - .get("HostConfig.PortBindings") - .unwrap_or(&json!(null)) - .as_object() - .unwrap_or(&Map::new()) - .iter() - { - port_bindings.insert(key.to_string(), json!(val)); - } - port_bindings.insert( - format!("{}/{}", srcport, protocol), - json!(vec![exposedport]), - ); - - self.params - .insert("HostConfig.PortBindings", json!(port_bindings)); - - // Replicate the port bindings over to the exposed ports config - let mut exposed_ports: HashMap = HashMap::new(); - let empty_config: HashMap = HashMap::new(); - for key in port_bindings.keys() { - exposed_ports.insert(key.to_string(), json!(empty_config)); - } - - self.params.insert("ExposedPorts", json!(exposed_ports)); - - self - } - - /// Publish a port in the container without assigning a port on the host - pub fn publish( - &mut self, - srcport: u32, - protocol: &str, - ) -> &mut Self { - /* The idea here is to go thought the 'old' port binds - * and to apply them to the local 'exposedport_bindings' variable, - * add the bind we want and replace the 'old' value */ - let mut exposed_port_bindings: HashMap = HashMap::new(); - for (key, val) in self - .params - .get("ExposedPorts") - .unwrap_or(&json!(null)) - .as_object() - .unwrap_or(&Map::new()) - .iter() - { - exposed_port_bindings.insert(key.to_string(), json!(val)); - } - exposed_port_bindings.insert(format!("{}/{}", srcport, protocol), json!({})); - - // Replicate the port bindings over to the exposed ports config - let mut exposed_ports: HashMap = HashMap::new(); - let empty_config: HashMap = HashMap::new(); - for key in exposed_port_bindings.keys() { - exposed_ports.insert(key.to_string(), json!(empty_config)); - } - - self.params.insert("ExposedPorts", json!(exposed_ports)); - - self - } - - pub fn links( - &mut self, - links: Vec<&str>, - ) -> &mut Self { - self.params.insert("HostConfig.Links", json!(links)); - self - } - - pub fn memory( - &mut self, - memory: u64, - ) -> &mut Self { - self.params.insert("HostConfig.Memory", json!(memory)); - self - } - - /// Total memory limit (memory + swap) in bytes. Set to -1 (default) to enable unlimited swap. - pub fn memory_swap( - &mut self, - memory_swap: i64, - ) -> &mut Self { - self.params - .insert("HostConfig.MemorySwap", json!(memory_swap)); - self - } - - /// CPU quota in units of 10-9 CPUs. Set to 0 (default) for there to be no limit. - /// - /// For example, setting `nano_cpus` to `500_000_000` results in the container being allocated - /// 50% of a single CPU, while `2_000_000_000` results in the container being allocated 2 CPUs. - pub fn nano_cpus( - &mut self, - nano_cpus: u64, - ) -> &mut Self { - self.params.insert("HostConfig.NanoCpus", json!(nano_cpus)); - self - } - - /// CPU quota in units of CPUs. This is a wrapper around `nano_cpus` to do the unit conversion. - /// - /// See [`nano_cpus`](#method.nano_cpus). - pub fn cpus( - &mut self, - cpus: f64, - ) -> &mut Self { - self.nano_cpus((1_000_000_000.0 * cpus) as u64) - } - - /// Sets an integer value representing the container's relative CPU weight versus other - /// containers. - pub fn cpu_shares( - &mut self, - cpu_shares: u32, - ) -> &mut Self { - self.params - .insert("HostConfig.CpuShares", json!(cpu_shares)); - self - } - - pub fn labels( - &mut self, - labels: &HashMap<&str, &str>, - ) -> &mut Self { - self.params.insert("Labels", json!(labels)); - self - } - - /// Whether to attach to `stdin`. - pub fn attach_stdin( - &mut self, - attach: bool, - ) -> &mut Self { - self.params.insert("AttachStdin", json!(attach)); - self.params.insert("OpenStdin", json!(attach)); - self - } - - /// Whether to attach to `stdout`. - pub fn attach_stdout( - &mut self, - attach: bool, - ) -> &mut Self { - self.params.insert("AttachStdout", json!(attach)); - self - } - - /// Whether to attach to `stderr`. - pub fn attach_stderr( - &mut self, - attach: bool, - ) -> &mut Self { - self.params.insert("AttachStderr", json!(attach)); - self - } - - /// Whether standard streams should be attached to a TTY. - pub fn tty( - &mut self, - tty: bool, - ) -> &mut Self { - self.params.insert("Tty", json!(tty)); - self - } - - pub fn extra_hosts( - &mut self, - hosts: Vec<&str>, - ) -> &mut Self { - self.params.insert("HostConfig.ExtraHosts", json!(hosts)); - self - } - - pub fn volumes_from( - &mut self, - volumes: Vec<&str>, - ) -> &mut Self { - self.params.insert("HostConfig.VolumesFrom", json!(volumes)); - self - } - - pub fn network_mode( - &mut self, - network: &str, - ) -> &mut Self { - self.params.insert("HostConfig.NetworkMode", json!(network)); - self - } - - pub fn env( - &mut self, - envs: E, - ) -> &mut Self - where - S: AsRef + Serialize, - E: AsRef<[S]> + Serialize, - { - self.params.insert("Env", json!(envs)); - self - } - - pub fn cmd( - &mut self, - cmds: Vec<&str>, - ) -> &mut Self { - self.params.insert("Cmd", json!(cmds)); - self - } - - pub fn entrypoint( - &mut self, - entrypoint: I, - ) -> &mut Self - where - I: IntoIterator + Serialize, - S: AsRef, - { - self.params.insert("Entrypoint", json!(entrypoint)); - self - } - - pub fn capabilities( - &mut self, - capabilities: Vec<&str>, - ) -> &mut Self { - self.params.insert("HostConfig.CapAdd", json!(capabilities)); - self - } - - pub fn devices( - &mut self, - devices: Vec>, - ) -> &mut Self { - self.params.insert("HostConfig.Devices", json!(devices)); - self - } - - pub fn log_driver( - &mut self, - log_driver: &str, - ) -> &mut Self { - self.params - .insert("HostConfig.LogConfig.Type", json!(log_driver)); - self - } - - pub fn restart_policy( - &mut self, - name: &str, - maximum_retry_count: u64, - ) -> &mut Self { - self.params - .insert("HostConfig.RestartPolicy.Name", json!(name)); - if name == "on-failure" { - self.params.insert( - "HostConfig.RestartPolicy.MaximumRetryCount", - json!(maximum_retry_count), - ); - } - self - } - - pub fn auto_remove( - &mut self, - set: bool, - ) -> &mut Self { - self.params.insert("HostConfig.AutoRemove", json!(set)); - self - } - - /// Signal to stop a container as a string. Default is "SIGTERM". - pub fn stop_signal( - &mut self, - sig: &str, - ) -> &mut Self { - self.params.insert("StopSignal", json!(sig)); - self - } - - /// Signal to stop a container as an integer. Default is 15 (SIGTERM). - pub fn stop_signal_num( - &mut self, - sig: u64, - ) -> &mut Self { - self.params.insert("StopSignal", json!(sig)); - self - } - - /// Timeout to stop a container. Only seconds are counted. Default is 10s - pub fn stop_timeout( - &mut self, - timeout: Duration, - ) -> &mut Self { - self.params.insert("StopTimeout", json!(timeout.as_secs())); - self - } - - pub fn userns_mode( - &mut self, - mode: &str, - ) -> &mut Self { - self.params.insert("HostConfig.UsernsMode", json!(mode)); - self - } - - pub fn privileged( - &mut self, - set: bool, - ) -> &mut Self { - self.params.insert("HostConfig.Privileged", json!(set)); - self - } - - pub fn user( - &mut self, - user: &str, - ) -> &mut Self { - self.params.insert("User", json!(user)); - self - } - - pub fn build(&self) -> ContainerOptions { - ContainerOptions { - name: self.name.clone(), - params: self.params.clone(), - } - } -} - -#[derive(Serialize, Debug)] -pub struct ExecContainerOptions { - params: HashMap<&'static str, Vec>, - params_bool: HashMap<&'static str, bool>, -} - -impl ExecContainerOptions { - /// return a new instance of a builder for options - pub fn builder() -> ExecContainerOptionsBuilder { - ExecContainerOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - let mut body = serde_json::Map::new(); - - for (k, v) in &self.params { - body.insert( - (*k).to_owned(), - serde_json::to_value(v).map_err(Error::SerdeJsonError)?, - ); - } - - for (k, v) in &self.params_bool { - body.insert( - (*k).to_owned(), - serde_json::to_value(v).map_err(Error::SerdeJsonError)?, - ); - } - - serde_json::to_string(&body).map_err(Error::from) - } -} - -#[derive(Default)] -pub struct ExecContainerOptionsBuilder { - params: HashMap<&'static str, Vec>, - params_bool: HashMap<&'static str, bool>, -} - -impl ExecContainerOptionsBuilder { - /// Command to run, as an array of strings - pub fn cmd( - &mut self, - cmds: Vec<&str>, - ) -> &mut Self { - for cmd in cmds { - self.params - .entry("Cmd") - .or_insert_with(Vec::new) - .push(cmd.to_owned()); - } - self - } - - /// A list of environment variables in the form "VAR=value" - pub fn env( - &mut self, - envs: Vec<&str>, - ) -> &mut Self { - for env in envs { - self.params - .entry("Env") - .or_insert_with(Vec::new) - .push(env.to_owned()); - } - self - } - - /// Attach to stdout of the exec command - pub fn attach_stdout( - &mut self, - stdout: bool, - ) -> &mut Self { - self.params_bool.insert("AttachStdout", stdout); - self - } - - /// Attach to stderr of the exec command - pub fn attach_stderr( - &mut self, - stderr: bool, - ) -> &mut Self { - self.params_bool.insert("AttachStderr", stderr); - self - } - - pub fn build(&self) -> ExecContainerOptions { - ExecContainerOptions { - params: self.params.clone(), - params_bool: self.params_bool.clone(), - } - } -} - -/// Options for filtering streams of Docker events -#[derive(Default, Debug)] -pub struct EventsOptions { - params: HashMap<&'static str, String>, -} - -impl EventsOptions { - pub fn builder() -> EventsOptionsBuilder { - EventsOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - if self.params.is_empty() { - None - } else { - Some( - form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&self.params) - .finish(), - ) - } - } -} - -#[derive(Copy, Clone)] -pub enum EventFilterType { - Container, - Image, - Volume, - Network, - Daemon, -} - -fn event_filter_type_to_string(filter: EventFilterType) -> &'static str { - match filter { - EventFilterType::Container => "container", - EventFilterType::Image => "image", - EventFilterType::Volume => "volume", - EventFilterType::Network => "network", - EventFilterType::Daemon => "daemon", - } -} - -/// Filter options for image listings -pub enum EventFilter { - Container(String), - Event(String), - Image(String), - Label(String), - Type(EventFilterType), - Volume(String), - Network(String), - Daemon(String), -} - -/// Builder interface for `EventOptions` -#[derive(Default)] -pub struct EventsOptionsBuilder { - params: HashMap<&'static str, String>, - events: Vec, - containers: Vec, - images: Vec, - labels: Vec, - volumes: Vec, - networks: Vec, - daemons: Vec, - types: Vec, -} - -impl EventsOptionsBuilder { - /// Filter events since a given timestamp - pub fn since( - &mut self, - ts: &u64, - ) -> &mut Self { - self.params.insert("since", ts.to_string()); - self - } - - /// Filter events until a given timestamp - pub fn until( - &mut self, - ts: &u64, - ) -> &mut Self { - self.params.insert("until", ts.to_string()); - self - } - - pub fn filter( - &mut self, - filters: Vec, - ) -> &mut Self { - let mut params = HashMap::new(); - for f in filters { - match f { - EventFilter::Container(n) => { - self.containers.push(n); - params.insert("container", self.containers.clone()) - } - EventFilter::Event(n) => { - self.events.push(n); - params.insert("event", self.events.clone()) - } - EventFilter::Image(n) => { - self.images.push(n); - params.insert("image", self.images.clone()) - } - EventFilter::Label(n) => { - self.labels.push(n); - params.insert("label", self.labels.clone()) - } - EventFilter::Volume(n) => { - self.volumes.push(n); - params.insert("volume", self.volumes.clone()) - } - EventFilter::Network(n) => { - self.networks.push(n); - params.insert("network", self.networks.clone()) - } - EventFilter::Daemon(n) => { - self.daemons.push(n); - params.insert("daemon", self.daemons.clone()) - } - EventFilter::Type(n) => { - let event_type = event_filter_type_to_string(n).to_string(); - self.types.push(event_type); - params.insert("type", self.types.clone()) - } - }; - } - self.params - .insert("filters", serde_json::to_string(¶ms).unwrap()); - self - } - - pub fn build(&self) -> EventsOptions { - EventsOptions { - params: self.params.clone(), - } - } -} - -/// Options for controlling log request results -#[derive(Default, Debug)] -pub struct LogsOptions { - params: HashMap<&'static str, String>, -} - -impl LogsOptions { - /// return a new instance of a builder for options - pub fn builder() -> LogsOptionsBuilder { - LogsOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - if self.params.is_empty() { - None - } else { - Some( - form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&self.params) - .finish(), - ) - } - } -} - -/// Builder interface for `LogsOptions` -#[derive(Default)] -pub struct LogsOptionsBuilder { - params: HashMap<&'static str, String>, -} - -impl LogsOptionsBuilder { - pub fn follow( - &mut self, - f: bool, - ) -> &mut Self { - self.params.insert("follow", f.to_string()); - self - } - - pub fn stdout( - &mut self, - s: bool, - ) -> &mut Self { - self.params.insert("stdout", s.to_string()); - self - } - - pub fn stderr( - &mut self, - s: bool, - ) -> &mut Self { - self.params.insert("stderr", s.to_string()); - self - } - - pub fn timestamps( - &mut self, - t: bool, - ) -> &mut Self { - self.params.insert("timestamps", t.to_string()); - self - } - - /// how_many can either be "all" or a to_string() of the number - pub fn tail( - &mut self, - how_many: &str, - ) -> &mut Self { - self.params.insert("tail", how_many.to_owned()); - self - } - - #[cfg(feature = "chrono")] - pub fn since( - &mut self, - timestamp: &chrono::DateTime, - ) -> &mut Self - where - Tz: chrono::TimeZone, - { - self.params - .insert("since", timestamp.timestamp().to_string()); - self - } - - #[cfg(not(feature = "chrono"))] - pub fn since( - &mut self, - timestamp: i64, - ) -> &mut Self { - self.params.insert("since", timestamp.to_string()); - self - } - - pub fn build(&self) -> LogsOptions { - LogsOptions { - 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 { - 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, - ) -> &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(), - } - } -} - -/// Options for controlling log request results -#[derive(Default, Debug)] -pub struct RmContainerOptions { - params: HashMap<&'static str, String>, -} - -impl RmContainerOptions { - /// return a new instance of a builder for options - pub fn builder() -> RmContainerOptionsBuilder { - RmContainerOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - if self.params.is_empty() { - None - } else { - Some( - form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&self.params) - .finish(), - ) - } - } -} - -/// Builder interface for `LogsOptions` -#[derive(Default)] -pub struct RmContainerOptionsBuilder { - params: HashMap<&'static str, String>, -} - -impl RmContainerOptionsBuilder { - pub fn force( - &mut self, - f: bool, - ) -> &mut Self { - self.params.insert("force", f.to_string()); - self - } - - pub fn volumes( - &mut self, - s: bool, - ) -> &mut Self { - self.params.insert("v", s.to_string()); - self - } - - pub fn build(&self) -> RmContainerOptions { - RmContainerOptions { - params: self.params.clone(), - } - } -} - -/// Options for filtering networks list results -#[derive(Default, Debug)] -pub struct NetworkListOptions { - params: HashMap<&'static str, String>, -} - -impl NetworkListOptions { - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - if self.params.is_empty() { - None - } else { - Some( - form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&self.params) - .finish(), - ) - } - } -} - -/// Interface for creating new docker network -#[derive(Serialize, Debug)] -pub struct NetworkCreateOptions { - params: HashMap<&'static str, Value>, -} - -impl NetworkCreateOptions { - /// return a new instance of a builder for options - pub fn builder(name: &str) -> NetworkCreateOptionsBuilder { - NetworkCreateOptionsBuilder::new(name) - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - serde_json::to_string(&self.params).map_err(Error::from) - } - - pub fn parse_from<'a, K, V>( - &self, - params: &'a HashMap, - body: &mut serde_json::Map, - ) where - &'a HashMap: IntoIterator, - K: ToString + Eq + Hash, - V: Serialize, - { - for (k, v) in params.iter() { - let key = k.to_string(); - let value = serde_json::to_value(v).unwrap(); - - body.insert(key, value); - } - } -} - -#[derive(Default)] -pub struct NetworkCreateOptionsBuilder { - params: HashMap<&'static str, Value>, -} - -impl NetworkCreateOptionsBuilder { - pub(crate) fn new(name: &str) -> Self { - let mut params = HashMap::new(); - params.insert("Name", json!(name)); - NetworkCreateOptionsBuilder { params } - } - - pub fn driver( - &mut self, - name: &str, - ) -> &mut Self { - if !name.is_empty() { - self.params.insert("Driver", json!(name)); - } - self - } - - pub fn label( - &mut self, - labels: HashMap, - ) -> &mut Self { - self.params.insert("Labels", json!(labels)); - self - } - - pub fn build(&self) -> NetworkCreateOptions { - NetworkCreateOptions { - params: self.params.clone(), - } - } -} - -/// Interface for connect container to network -#[derive(Serialize, Debug)] -pub struct ContainerConnectionOptions { - params: HashMap<&'static str, Value>, -} - -impl ContainerConnectionOptions { - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - serde_json::to_string(&self.params).map_err(Error::from) - } - - pub fn parse_from<'a, K, V>( - &self, - params: &'a HashMap, - body: &mut BTreeMap, - ) where - &'a HashMap: IntoIterator, - K: ToString + Eq + Hash, - V: Serialize, - { - for (k, v) in params.iter() { - let key = k.to_string(); - let value = serde_json::to_value(v).unwrap(); - - body.insert(key, value); - } - } - - /// return a new instance of a builder for options - pub fn builder(container_id: &str) -> ContainerConnectionOptionsBuilder { - ContainerConnectionOptionsBuilder::new(container_id) - } -} - -#[derive(Default)] -pub struct ContainerConnectionOptionsBuilder { - params: HashMap<&'static str, Value>, -} - -impl ContainerConnectionOptionsBuilder { - pub(crate) fn new(container_id: &str) -> Self { - let mut params = HashMap::new(); - params.insert("Container", json!(container_id)); - ContainerConnectionOptionsBuilder { params } - } - - pub fn aliases( - &mut self, - aliases: Vec<&str>, - ) -> &mut Self { - self.params - .insert("EndpointConfig", json!({ "Aliases": json!(aliases) })); - self - } - - pub fn force(&mut self) -> &mut Self { - self.params.insert("Force", json!(true)); - self - } - - pub fn build(&self) -> ContainerConnectionOptions { - ContainerConnectionOptions { - params: self.params.clone(), - } - } -} - -/// Interface for creating volumes -#[derive(Serialize, Debug)] -pub struct VolumeCreateOptions { - params: HashMap<&'static str, Value>, -} - -impl VolumeCreateOptions { - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - serde_json::to_string(&self.params).map_err(Error::from) - } - - pub fn parse_from<'a, K, V>( - &self, - params: &'a HashMap, - body: &mut BTreeMap, - ) where - &'a HashMap: IntoIterator, - K: ToString + Eq + Hash, - V: Serialize, - { - for (k, v) in params.iter() { - let key = k.to_string(); - let value = serde_json::to_value(v).unwrap(); - - body.insert(key, value); - } - } - - /// return a new instance of a builder for options - pub fn builder() -> VolumeCreateOptionsBuilder { - VolumeCreateOptionsBuilder::new() - } -} - -#[derive(Default)] -pub struct VolumeCreateOptionsBuilder { - params: HashMap<&'static str, Value>, -} - -impl VolumeCreateOptionsBuilder { - pub(crate) fn new() -> Self { - let params = HashMap::new(); - VolumeCreateOptionsBuilder { params } - } - - pub fn name( - &mut self, - name: &str, - ) -> &mut Self { - self.params.insert("Name", json!(name)); - self - } - - pub fn labels( - &mut self, - labels: &HashMap<&str, &str>, - ) -> &mut Self { - self.params.insert("Labels", json!(labels)); - self - } - - pub fn build(&self) -> VolumeCreateOptions { - VolumeCreateOptions { - params: self.params.clone(), - } - } -} -/// -/// Interface for creating volumes -#[derive(Serialize, Debug)] -pub struct ExecResizeOptions { - params: HashMap<&'static str, Value>, -} - -impl ExecResizeOptions { - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - serde_json::to_string(&self.params).map_err(Error::from) - } - - pub fn parse_from<'a, K, V>( - &self, - params: &'a HashMap, - body: &mut BTreeMap, - ) where - &'a HashMap: IntoIterator, - K: ToString + Eq + Hash, - V: Serialize, - { - for (k, v) in params.iter() { - let key = k.to_string(); - let value = serde_json::to_value(v).unwrap(); - - body.insert(key, value); - } - } - - /// return a new instance of a builder for options - pub fn builder() -> ExecResizeOptionsBuilder { - ExecResizeOptionsBuilder::new() - } -} - -#[derive(Default)] -pub struct ExecResizeOptionsBuilder { - params: HashMap<&'static str, Value>, -} - -impl ExecResizeOptionsBuilder { - pub(crate) fn new() -> Self { - let params = HashMap::new(); - ExecResizeOptionsBuilder { params } - } - - pub fn height( - &mut self, - height: u64, - ) -> &mut Self { - self.params.insert("Name", json!(height)); - self - } - - pub fn width( - &mut self, - width: u64, - ) -> &mut Self { - self.params.insert("Name", json!(width)); - self - } - - pub fn build(&self) -> ExecResizeOptions { - ExecResizeOptions { - params: self.params.clone(), - } - } -} - -//################################################################################ -// Services -//################################################################################ - -/// Options for filtering services list results -#[derive(Default, Debug)] -pub struct ServiceListOptions { - params: HashMap<&'static str, String>, -} - -impl ServiceListOptions { - /// return a new instance of a builder for options - pub fn builder() -> ServiceListOptionsBuilder { - ServiceListOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Option { - if self.params.is_empty() { - None - } else { - Some( - form_urlencoded::Serializer::new(String::new()) - .extend_pairs(&self.params) - .finish(), - ) - } - } -} - -/// Filter options for services listings -pub enum ServiceFilter { - Id(String), - Label(String), - ReplicatedMode, - GlobalMode, - Name(String), -} - -/// Builder interface for `ServicesListOptions` -#[derive(Default)] -pub struct ServiceListOptionsBuilder { - params: HashMap<&'static str, String>, -} - -impl ServiceListOptionsBuilder { - pub fn filter( - &mut self, - filters: Vec, - ) -> &mut Self { - let mut param = HashMap::new(); - for f in filters { - match f { - ServiceFilter::Id(i) => param.insert("id", vec![i]), - ServiceFilter::Label(l) => param.insert("label", vec![l]), - ServiceFilter::ReplicatedMode => { - param.insert("mode", vec!["replicated".to_string()]) - } - ServiceFilter::GlobalMode => param.insert("mode", vec!["global".to_string()]), - ServiceFilter::Name(n) => param.insert("name", vec![n.to_string()]), - }; - } - // 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 enable_status(&mut self) -> &mut Self { - self.params.insert("status", "true".to_owned()); - self - } - - pub fn build(&self) -> ServiceListOptions { - ServiceListOptions { - params: self.params.clone(), - } - } -} - -#[derive(Default, Debug)] -pub struct ServiceOptions { - auth: Option, - params: HashMap<&'static str, Value>, -} - -impl ServiceOptions { - /// return a new instance of a builder for options - pub fn builder() -> ServiceOptionsBuilder { - ServiceOptionsBuilder::default() - } - - /// serialize options as a string. returns None if no options are defined - pub fn serialize(&self) -> Result { - serde_json::to_string(&self.params).map_err(Error::from) - } - - pub(crate) fn auth_header(&self) -> Option { - self.auth.clone().map(|a| a.serialize()) - } -} - -#[derive(Default)] -pub struct ServiceOptionsBuilder { - auth: Option, - params: HashMap<&'static str, Result>, -} - -impl ServiceOptionsBuilder { - pub fn name( - &mut self, - name: S, - ) -> &mut Self - where - S: AsRef, - { - self.params.insert("Name", Ok(json!(name.as_ref()))); - self - } - - pub fn labels( - &mut self, - labels: I, - ) -> &mut Self - where - I: IntoIterator, - { - self.params.insert( - "Labels", - Ok(json!(labels - .into_iter() - .collect::>())), - ); - self - } - - pub fn task_template( - &mut self, - spec: &TaskSpec, - ) -> &mut Self { - self.params.insert("TaskTemplate", to_json_value(spec)); - self - } - - pub fn mode( - &mut self, - mode: &Mode, - ) -> &mut Self { - self.params.insert("Mode", to_json_value(mode)); - self - } - - pub fn update_config( - &mut self, - conf: &UpdateConfig, - ) -> &mut Self { - self.params.insert("UpdateConfig", to_json_value(conf)); - self - } - - pub fn rollback_config( - &mut self, - conf: &RollbackConfig, - ) -> &mut Self { - self.params.insert("RollbackConfig", to_json_value(conf)); - self - } - - pub fn networks( - &mut self, - networks: I, - ) -> &mut Self - where - I: IntoIterator, - { - self.params.insert( - "Networks", - to_json_value( - networks - .into_iter() - .collect::>(), - ), - ); - self - } - - pub fn endpoint_spec( - &mut self, - spec: &EndpointSpec, - ) -> &mut Self { - self.params.insert("EndpointSpec", to_json_value(spec)); - self - } - - pub fn auth( - &mut self, - auth: RegistryAuth, - ) -> &mut Self { - self.auth = Some(auth); - self - } - - pub fn build(&mut self) -> Result { - let params = std::mem::take(&mut self.params); - let mut new_params = HashMap::new(); - for (k, v) in params.into_iter() { - new_params.insert(k, v?); - } - Ok(ServiceOptions { - auth: self.auth.take(), - params: new_params, - }) - } -} - -fn to_json_value(value: T) -> Result -where - T: Serialize, -{ - Ok(serde_json::to_value(value)?) -} - -//################################################################################ - -#[cfg(test)] -mod tests { - use super::{ContainerOptionsBuilder, LogsOptionsBuilder, RegistryAuth}; - - #[test] - fn container_options_simple() { - let builder = ContainerOptionsBuilder::new("test_image"); - let options = builder.build(); - - assert_eq!( - r#"{"HostConfig":{},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - #[test] - fn container_options_env() { - let options = ContainerOptionsBuilder::new("test_image") - .env(vec!["foo", "bar"]) - .build(); - - assert_eq!( - r#"{"Env":["foo","bar"],"HostConfig":{},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - #[test] - fn container_options_env_dynamic() { - let env: Vec = ["foo", "bar", "baz"] - .iter() - .map(|s| String::from(*s)) - .collect(); - - let options = ContainerOptionsBuilder::new("test_image").env(&env).build(); - - assert_eq!( - r#"{"Env":["foo","bar","baz"],"HostConfig":{},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - #[test] - fn container_options_user() { - let options = ContainerOptionsBuilder::new("test_image") - .user("alice") - .build(); - - assert_eq!( - r#"{"HostConfig":{},"Image":"test_image","User":"alice"}"#, - options.serialize().unwrap() - ); - } - - #[test] - fn container_options_host_config() { - let options = ContainerOptionsBuilder::new("test_image") - .network_mode("host") - .auto_remove(true) - .privileged(true) - .build(); - - assert_eq!( - r#"{"HostConfig":{"AutoRemove":true,"NetworkMode":"host","Privileged":true},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - #[test] - fn container_options_expose() { - let options = ContainerOptionsBuilder::new("test_image") - .expose(80, "tcp", 8080) - .build(); - assert_eq!( - r#"{"ExposedPorts":{"80/tcp":{}},"HostConfig":{"PortBindings":{"80/tcp":[{"HostPort":"8080"}]}},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - // try exposing two - let options = ContainerOptionsBuilder::new("test_image") - .expose(80, "tcp", 8080) - .expose(81, "tcp", 8081) - .build(); - assert_eq!( - r#"{"ExposedPorts":{"80/tcp":{},"81/tcp":{}},"HostConfig":{"PortBindings":{"80/tcp":[{"HostPort":"8080"}],"81/tcp":[{"HostPort":"8081"}]}},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - #[test] - fn container_options_publish() { - let options = ContainerOptionsBuilder::new("test_image") - .publish(80, "tcp") - .build(); - assert_eq!( - r#"{"ExposedPorts":{"80/tcp":{}},"HostConfig":{},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - // try exposing two - let options = ContainerOptionsBuilder::new("test_image") - .publish(80, "tcp") - .publish(81, "tcp") - .build(); - assert_eq!( - r#"{"ExposedPorts":{"80/tcp":{},"81/tcp":{}},"HostConfig":{},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - /// Test container option PublishAllPorts - #[test] - fn container_options_publish_all_ports() { - let options = ContainerOptionsBuilder::new("test_image") - .publish_all_ports() - .build(); - - assert_eq!( - r#"{"HostConfig":{"PublishAllPorts":true},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - /// Test container options that are nested 3 levels deep. - #[test] - fn container_options_nested() { - let options = ContainerOptionsBuilder::new("test_image") - .log_driver("fluentd") - .build(); - - assert_eq!( - r#"{"HostConfig":{"LogConfig":{"Type":"fluentd"}},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - /// Test the restart policy settings - #[test] - fn container_options_restart_policy() { - let mut options = ContainerOptionsBuilder::new("test_image") - .restart_policy("on-failure", 10) - .build(); - - assert_eq!( - r#"{"HostConfig":{"RestartPolicy":{"MaximumRetryCount":10,"Name":"on-failure"}},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - - options = ContainerOptionsBuilder::new("test_image") - .restart_policy("always", 0) - .build(); - - assert_eq!( - r#"{"HostConfig":{"RestartPolicy":{"Name":"always"}},"Image":"test_image"}"#, - options.serialize().unwrap() - ); - } - - /// 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() - ); - } - - #[cfg(feature = "chrono")] - #[test] - fn logs_options() { - let timestamp = chrono::NaiveDateTime::from_timestamp(2_147_483_647, 0); - let since = chrono::DateTime::::from_utc(timestamp, chrono::Utc); - - let options = LogsOptionsBuilder::default() - .follow(true) - .stdout(true) - .stderr(true) - .timestamps(true) - .tail("all") - .since(&since) - .build(); - - let serialized = options.serialize().unwrap(); - - assert!(serialized.contains("follow=true")); - assert!(serialized.contains("stdout=true")); - assert!(serialized.contains("stderr=true")); - assert!(serialized.contains("timestamps=true")); - assert!(serialized.contains("tail=all")); - assert!(serialized.contains("since=2147483647")); - } - - #[cfg(not(feature = "chrono"))] - #[test] - fn logs_options() { - let options = LogsOptionsBuilder::default() - .follow(true) - .stdout(true) - .stderr(true) - .timestamps(true) - .tail("all") - .since(2_147_483_647) - .build(); - - let serialized = options.serialize().unwrap(); - - assert!(serialized.contains("follow=true")); - assert!(serialized.contains("stdout=true")); - assert!(serialized.contains("stderr=true")); - assert!(serialized.contains("timestamps=true")); - assert!(serialized.contains("tail=all")); - assert!(serialized.contains("since=2147483647")); - } -} diff --git a/src/container.rs b/src/container.rs index ea4d0a7..d796c73 100644 --- a/src/container.rs +++ b/src/container.rs @@ -2,7 +2,7 @@ //! //! API Reference: -use std::{io, path::Path, time::Duration}; +use std::{collections::HashMap, hash::Hash, io, iter::Peekable, path::Path, time::Duration}; use futures_util::{ io::{AsyncRead, AsyncWrite}, @@ -11,16 +11,14 @@ use futures_util::{ }; use hyper::Body; use mime::Mime; +use serde::Serialize; +use serde_json::{json, Map, Value}; use url::form_urlencoded; use crate::{ - builder::{ - ContainerListOptions, ContainerOptions, ExecContainerOptions, LogsOptions, - RmContainerOptions, - }, docker::Docker, errors::{Error, Result}, - exec::Exec, + exec::{Exec, ExecContainerOptions}, rep::{ Change, Container as ContainerInfo, ContainerCreateInfo, ContainerDetails, Exit, Stats, Top, }, @@ -408,3 +406,919 @@ impl<'docker> Containers<'docker> { .await } } + +/// Options for filtering container list results +#[derive(Default, Debug)] +pub struct ContainerListOptions { + params: HashMap<&'static str, String>, +} + +impl ContainerListOptions { + /// return a new instance of a builder for options + pub fn builder() -> ContainerListOptionsBuilder { + ContainerListOptionsBuilder::default() + } + + /// serialize options as a string. returns None if no options are defined + pub fn serialize(&self) -> Option { + if self.params.is_empty() { + None + } else { + Some( + form_urlencoded::Serializer::new(String::new()) + .extend_pairs(&self.params) + .finish(), + ) + } + } +} + +/// Filter options for container listings +pub enum ContainerFilter { + ExitCode(u64), + Status(String), + LabelName(String), + Label(String, String), +} + +/// Builder interface for `ContainerListOptions` +#[derive(Default)] +pub struct ContainerListOptionsBuilder { + params: HashMap<&'static str, String>, +} + +impl ContainerListOptionsBuilder { + pub fn filter( + &mut self, + filters: Vec, + ) -> &mut Self { + let mut param = HashMap::new(); + for f in filters { + match f { + ContainerFilter::ExitCode(c) => param.insert("exit", vec![c.to_string()]), + ContainerFilter::Status(s) => param.insert("status", vec![s]), + ContainerFilter::LabelName(n) => param.insert("label", vec![n]), + ContainerFilter::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 all(&mut self) -> &mut Self { + self.params.insert("all", "true".to_owned()); + self + } + + pub fn since( + &mut self, + since: &str, + ) -> &mut Self { + self.params.insert("since", since.to_owned()); + self + } + + pub fn before( + &mut self, + before: &str, + ) -> &mut Self { + self.params.insert("before", before.to_owned()); + self + } + + pub fn sized(&mut self) -> &mut Self { + self.params.insert("size", "true".to_owned()); + self + } + + pub fn build(&self) -> ContainerListOptions { + ContainerListOptions { + params: self.params.clone(), + } + } +} + +/// Interface for building a new docker container from an existing image +#[de