diff options
Diffstat (limited to 'src/container.rs')
-rw-r--r-- | src/container.rs | 152 |
1 files changed, 117 insertions, 35 deletions
diff --git a/src/container.rs b/src/container.rs index ec2033d..ad26238 100644 --- a/src/container.rs +++ b/src/container.rs @@ -20,7 +20,7 @@ use crate::{ errors::{Error, Result}, exec::{Exec, ExecContainerOptions}, image::Config, - network::{NetworkInfo, NetworkSettings}, + network::NetworkSettings, transport::Payload, tty::{self, Multiplexer as TtyMultiPlexer}, }; @@ -32,7 +32,7 @@ use chrono::{DateTime, Utc}; /// Interface for accessing and manipulating a docker container /// -/// Api Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Container> +/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Container) pub struct Container<'docker> { docker: &'docker Docker, id: String, @@ -60,7 +60,7 @@ impl<'docker> Container<'docker> { /// Inspects the current docker container instance's details /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerInspect> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerInspect) pub async fn inspect(&self) -> Result<ContainerDetails> { self.docker .get_json::<ContainerDetails>(&format!("/containers/{}/json", self.id)[..]) @@ -69,7 +69,7 @@ impl<'docker> Container<'docker> { /// Returns a `top` view of information about the container process /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerTop> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerTop) pub async fn top( &self, psargs: Option<&str>, @@ -86,7 +86,7 @@ impl<'docker> Container<'docker> { /// Returns a stream of logs emitted but the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerLogs> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerLogs) pub fn logs( &self, opts: &LogsOptions, @@ -114,13 +114,15 @@ impl<'docker> Container<'docker> { .await } - /// Attaches a `[TtyMultiplexer]` to the container. + /// Attaches a [Multiplexer](crate::tty::Multiplexer) to the container. /// - /// The `[TtyMultiplexer]` implements Stream for returning Stdout and Stderr chunks. It also implements `[AsyncWrite]` for writing to Stdin. + /// The [Multiplexer](crate::tty::Multiplexer) implements Stream for returning Stdout and + /// Stderr chunks. It also implements `[AsyncWrite]` for writing to Stdin. /// - /// The multiplexer can be split into its read and write halves with the `[split](TtyMultiplexer::split)` method + /// The multiplexer can be split into its read and write halves with the + /// [split](crate::tty::Multiplexer::split) method /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerAttach) pub async fn attach(&self) -> Result<TtyMultiPlexer<'docker>> { let tcp_stream = self.attach_raw().await?; @@ -129,7 +131,7 @@ impl<'docker> Container<'docker> { /// Returns a set of changes made to the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerChanges> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerChanges) pub async fn changes(&self) -> Result<Vec<Change>> { self.docker .get_json::<Vec<Change>>(&format!("/containers/{}/changes", self.id)[..]) @@ -138,7 +140,7 @@ impl<'docker> Container<'docker> { /// Exports the current docker container into a tarball /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerExport> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerExport) pub fn export(&self) -> impl Stream<Item = Result<Vec<u8>>> + 'docker { self.docker .stream_get(format!("/containers/{}/export", self.id)) @@ -147,7 +149,7 @@ impl<'docker> Container<'docker> { /// Returns a stream of stats specific to this container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerStats> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStats) pub fn stats(&self) -> impl Stream<Item = Result<Stats>> + Unpin + 'docker { let codec = futures_codec::LinesCodec {}; @@ -169,7 +171,7 @@ impl<'docker> Container<'docker> { /// Start the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerStart> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStart) pub async fn start(&self) -> Result<()> { self.docker .post(&format!("/containers/{}/start", self.id)[..], None) @@ -179,7 +181,7 @@ impl<'docker> Container<'docker> { /// Stop the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerStop> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerStop) pub async fn stop( &self, wait: Option<Duration>, @@ -198,7 +200,7 @@ impl<'docker> Container<'docker> { /// Restart the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerRestart> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRestart) pub async fn restart( &self, wait: Option<Duration>, @@ -216,7 +218,7 @@ impl<'docker> Container<'docker> { /// Kill the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerKill> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerKill) pub async fn kill( &self, signal: Option<&str>, @@ -234,7 +236,7 @@ impl<'docker> Container<'docker> { /// Rename the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerRename> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRename) pub async fn rename( &self, name: &str, @@ -253,7 +255,7 @@ impl<'docker> Container<'docker> { /// Pause the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerPause> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerPause) pub async fn pause(&self) -> Result<()> { self.docker .post(&format!("/containers/{}/pause", self.id)[..], None) @@ -263,7 +265,7 @@ impl<'docker> Container<'docker> { /// Unpause the container instance /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerUnpause> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerUnpause) pub async fn unpause(&self) -> Result<()> { self.docker .post(&format!("/containers/{}/unpause", self.id)[..], None) @@ -273,7 +275,7 @@ impl<'docker> Container<'docker> { /// Wait until the container stops /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerWait> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerWait) pub async fn wait(&self) -> Result<Exit> { self.docker .post_json(format!("/containers/{}/wait", self.id), Payload::None) @@ -284,7 +286,7 @@ impl<'docker> Container<'docker> { /// /// Use remove instead to use the force/v options. /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerDelete> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerDelete) pub async fn delete(&self) -> Result<()> { self.docker .delete(&format!("/containers/{}", self.id)[..]) @@ -294,7 +296,7 @@ impl<'docker> Container<'docker> { /// Delete the container instance (todo: force/v) /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerRemove> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerRemove) pub async fn remove( &self, opts: RmContainerOptions, @@ -309,7 +311,7 @@ impl<'docker> Container<'docker> { /// Execute a command in this container /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Exec> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Exec) pub fn exec( &self, opts: &ExecContainerOptions, @@ -326,7 +328,7 @@ impl<'docker> Container<'docker> { /// ends in `/.` then this indicates that only the contents of the path directory should be /// copied. A symlink is always resolved to its target. /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerArchive> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerArchive) pub fn copy_from( &self, path: &Path, @@ -344,7 +346,7 @@ impl<'docker> Container<'docker> { /// The file will be copied at the given location (see `path`) and will be owned by root /// with access mask 644. /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive) pub async fn copy_file_into<P: AsRef<Path>>( &self, path: P, @@ -374,7 +376,7 @@ impl<'docker> Container<'docker> { /// /// The tarball will be copied to the container and extracted at the given location (see `path`). /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/PutContainerArchive) pub async fn copy_to( &self, path: &Path, @@ -398,7 +400,7 @@ impl<'docker> Container<'docker> { /// Interface for docker containers /// -/// Api Reference: <https://docs.docker.com/engine/api/v1.41/#tag/Containers> +/// [Api Reference](https://docs.docker.com/engine/api/v1.41/#tag/Containers) pub struct Containers<'docker> { docker: &'docker Docker, } @@ -411,7 +413,7 @@ impl<'docker> Containers<'docker> { /// Lists the container instances on the docker host /// - /// Api Reference: <https://docs.docker.com/engine/api/v1.41/#operation/ContainerList> + /// [Api Reference](https://docs.docker.com/engine/api/v1.41/#operation/ContainerList) pub async fn list( &self, opts: &ContainerListOptions, @@ -490,6 +492,7 @@ pub enum ContainerFilter { Status(String), LabelName(String), Label(String, String), + Name(String), } /// Builder interface for `ContainerListOptions` @@ -503,14 +506,17 @@ impl ContainerListOptionsBuilder { &mut self, filters: Vec<ContainerFilter>, ) -> &mut Self { - let mut param = HashMap::new(); + let mut param: HashMap<&str, Vec<String>> = 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)]), + let (key, value) = match f { + ContainerFilter::ExitCode(c) => ("exited", c.to_string()), + ContainerFilter::Status(s) => ("status", s), + ContainerFilter::LabelName(n) => ("label", n), + ContainerFilter::Label(n, v) => ("label", format!("{}={}", n, v)), + ContainerFilter::Name(n) => ("name", n.to_string()), }; + + param.entry(key).or_insert_with(Vec::new).push(value); } // structure is a a json encoded object mapping string keys to a list // of string values @@ -1287,13 +1293,25 @@ pub struct Port { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Stats { pub read: String, - pub networks: HashMap<String, NetworkInfo>, + pub networks: HashMap<String, NetworkStats>, pub memory_stats: MemoryStats, pub blkio_stats: BlkioStats, pub cpu_stats: CpuStats, } #[derive(Clone, Debug, Serialize, Deserialize)] +pub struct NetworkStats { + pub rx_dropped: u64, + pub rx_bytes: u64, + pub rx_errors: u64, + pub tx_packets: u64, + pub tx_dropped: u64, + pub rx_packets: u64, + pub tx_errors: u64, + pub tx_bytes: u64, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct MemoryStats { pub max_usage: u64, pub usage: u64, @@ -1408,6 +1426,7 @@ pub struct Exit { #[cfg(test)] mod tests { use super::*; + use crate::container::ContainerFilter::{ExitCode, Label, LabelName, Status}; #[test] fn container_options_simple() { @@ -1561,6 +1580,69 @@ mod tests { ); } + #[test] + fn container_list_options_multiple_labels() { + let options = ContainerListOptions::builder() + .filter(vec![ + Label("label1".to_string(), "value".to_string()), + LabelName("label2".to_string()), + ]) + .build(); + + let form = form_urlencoded::Serializer::new(String::new()) + .append_pair("filters", r#"{"label":["label1=value","label2"]}"#) + .finish(); + + assert_eq!(form, options.serialize().unwrap()) + } + + #[test] + fn container_list_options_exit_code() { + let options = ContainerListOptions::builder() + .filter(vec![ExitCode(0)]) + .build(); + + let form = form_urlencoded::Serializer::new(String::new()) + .append_pair("filters", r#"{"exited":["0"]}"#) + .finish(); + + assert_eq!(form, options.serialize().unwrap()) + } + + #[test] + fn container_list_options_status() { + let options = ContainerListOptions::builder() + .filter(vec![Status("running".to_string())]) + .build(); + + let form = form_urlencoded::Serializer::new(String::new()) + .append_pair("filters", r#"{"status":["running"]}"#) + .finish(); + + assert_eq!(form, options.serialize().unwrap()) + } + + #[test] + fn container_list_options_combined() { + let options = ContainerListOptions::builder() + .all() + .filter(vec![ + Label("label1".to_string(), "value".to_string()), + LabelName("label2".to_string()), + ExitCode(0), + Status("running".to_string()), + ]) + .build(); + + let serialized = options.serialize().unwrap(); + + assert!(serialized.contains("all=true")); + assert!(serialized.contains("filters=")); + assert!(serialized.contains("%22label%22%3A%5B%22label1%3Dvalue%22%2C%22label2%22%5D")); + assert!(serialized.contains("%22status%22%3A%5B%22running%22%5D")); + assert!(serialized.contains("%22exited%22%3A%5B%220%22%5D")); + } + #[cfg(feature = "chrono")] #[test] fn logs_options() { |