diff --git a/Cargo.toml b/Cargo.toml index 0fda5ffa..d70e7ebe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "bollard" description = "An asynchronous Docker daemon API" -version = "0.10.1" +version = "0.11.0" authors = [ "Bollard contributors" ] license = "Apache-2.0" homepage = "https://github.com/fussybeaver/bollard" @@ -19,14 +19,14 @@ test_ssl = ["ssl"] # Enable tests specifically for macos test_macos = [] # Enable rustls / ssl -ssl = ["hyper-rustls", "rustls", "rustls-native-certs", "webpki-roots"] +ssl = ["hyper-rustls", "rustls", "rustls-native-certs", "webpki-roots", "ct-logs"] [dependencies] base64 = "0.13" bollard-stubs = { version = "1.41.0" } bytes = "1" chrono = { version = "0.4", features = ["serde"] } -ct-logs = "0.8.0" +ct-logs = { version = "0.8.0", optional = true } dirs-next = "2.0" futures-core = "0.3" futures-util = "0.3" @@ -42,7 +42,7 @@ serde = "1.0" serde_derive = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -tokio = { version = "1.2", features = ["time", "fs", "net", "rt", "rt-multi-thread", "io-util"] } +tokio = { version = "1.7", features = ["time", "fs", "net", "rt", "rt-multi-thread", "io-util"] } thiserror = "1.0" tokio-util = { version = "0.6", features = ["codec"] } url = "2.2" @@ -52,11 +52,13 @@ webpki-roots = { version = "0.21", optional = true } env_logger = "0.8" flate2 = "1.0" tar = "0.4" -tokio = { version = "1.2", features = ["time", "fs", "net", "rt", "rt-multi-thread", "macros", "io-std"] } -termion = "1.5" +tokio = { version = "1.7", features = ["time", "fs", "net", "rt", "rt-multi-thread", "macros", "io-std"] } [target.'cfg(unix)'.dependencies] -hyperlocal = { version = "0.2.2", package = "hyper-unix-connector" } +hyperlocal = { version = "0.8.0" } + +[target.'cfg(unix)'.dev-dependencies] +termion = "1.5" [target.'cfg(windows)'.dependencies] winapi = "0.3.9" diff --git a/README.md b/README.md index 53513e4d..3c3b41a7 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Bollard leverages the latest [Hyper](https://github.com/hyperium/hyper) and [Tokio](https://github.com/tokio-rs/tokio) improvements for an asynchronous API containing futures, streams and the async/await paradigm. -The library also features Windows support through Named Pipes (disabled in 0.10, see below) and -HTTPS support through optional rustls bindings. +The library also features Windows support through Named Pipes and HTTPS support through +optional rustls bindings. ## Install @@ -19,7 +19,7 @@ Add the following to your `Cargo.toml` file ```nocompile [dependencies] -bollard = "0.9" +bollard = "0.11" ``` ## API @@ -27,18 +27,16 @@ bollard = "0.9" [API docs](crate). -Version 0.10 disables Named Pipe Windows support until the upstream Tokio project re-adds -support for Named Pipes. Please follow the [tracking -issue](https://github.com/tokio-rs/tokio/issues/3511) for updates on this. +Version 0.11 re-enables Windows Named Pipe support. As of version 0.6, this project now generates API stubs from the upstream Docker-maintained -[Swagger OpenAPI specification](https://docs.docker.com/engine/api/v1.40.yaml). The generated +[Swagger OpenAPI specification](https://docs.docker.com/engine/api/v1.41.yaml). The generated models are committed to this repository, but packaged in a separate crate [bollard-stubs](https://crates.io/crates/bollard-stubs). ### Version -The [Docker API](https://docs.docker.com/engine/api/v1.40/) is pegged at version `1.40`. The +The [Docker API](https://docs.docker.com/engine/api/v1.41/) is pegged at version `1.41`. The library also supports [version negotiation](https://docs.rs/bollard/latest/bollard/struct.Docker.html#method.negotiate_version), to allow downgrading to an older API version. @@ -49,15 +47,16 @@ to allow downgrading to an older API version. Connect to the docker server according to your architecture and security remit. -#### Unix socket +#### Socket -The client will connect to the standard unix socket location `/var/run/docker.sock`. Use the -`Docker::connect_with_unix` method API to parameterise the interface. +The client will connect to the standard unix socket location `/var/run/docker.sock` or windows +named pipe location `//./pipe/docker_engine`. Use the `Docker::connect_with_socket` method API +to parameterise the interface. ```rust use bollard::Docker; #[cfg(unix)] -Docker::connect_with_unix_defaults(); +Docker::connect_with_socket_defaults(); ``` #### Local diff --git a/appveyor.yml b/appveyor.yml index 56c34fb0..af6766b6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,4 +39,4 @@ test_script: - ps: Set-Item -path env:RUST_BACKTRACE -value 1 - ps: Set-Item -path env:RUST_LOG -value "hyper=trace,bollard=debug" - ps: Set-Item -path env:REGISTRY_HTTP_ADDR -value localhost:5000 - # - cargo test --verbose -- --nocapture --test-threads 1 + - cargo test --verbose -- --nocapture --test-threads 1 diff --git a/examples/build.rs b/examples/build.rs index b9ca135d..300c8c22 100644 --- a/examples/build.rs +++ b/examples/build.rs @@ -9,7 +9,7 @@ use futures_util::stream::StreamExt; #[tokio::main] async fn main() { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); let mut build_image_args = HashMap::new(); build_image_args.insert("dummy", "value"); diff --git a/examples/error.rs b/examples/error.rs index 75d18082..c7f469cc 100644 --- a/examples/error.rs +++ b/examples/error.rs @@ -4,7 +4,7 @@ extern crate bollard; use bollard::Docker; fn run() -> Result<(), Box> { - let _docker1 = Docker::connect_with_unix_defaults()?; + let _docker = Docker::connect_with_socket_defaults().unwrap(); let _env_var = std::env::var("ZOOKEEPER_ADDR")?; diff --git a/examples/exec.rs b/examples/exec.rs index aedaf83d..8df20dbf 100644 --- a/examples/exec.rs +++ b/examples/exec.rs @@ -12,7 +12,7 @@ const IMAGE: &'static str = "alpine:3"; #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); docker .create_image( diff --git a/examples/exec_term.rs b/examples/exec_term.rs index d0f34460..03a7dd1b 100644 --- a/examples/exec_term.rs +++ b/examples/exec_term.rs @@ -9,7 +9,9 @@ use bollard::image::CreateImageOptions; use futures_util::{StreamExt, TryStreamExt}; use std::io::{stdout, Read, Write}; use std::time::Duration; +#[cfg(not(windows))] use termion::raw::IntoRawMode; +#[cfg(not(windows))] use termion::{async_stdin, terminal_size}; use tokio::io::AsyncWriteExt; use tokio::task::spawn; @@ -19,7 +21,9 @@ const IMAGE: &'static str = "alpine:3"; #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); + + #[cfg(not(windows))] let tty_size = terminal_size()?; docker @@ -60,6 +64,7 @@ async fn main() -> Result<(), Box> { ) .await? .id; + #[cfg(not(windows))] if let StartExecResults::Attached { mut output, mut input, diff --git a/examples/hoover.rs b/examples/hoover.rs index b2f8af72..07c519c8 100644 --- a/examples/hoover.rs +++ b/examples/hoover.rs @@ -10,7 +10,7 @@ const THRESHOLD_DAYS: i64 = 90; #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults()?; + let docker = Docker::connect_with_socket_defaults().unwrap(); let date = Utc::now() - Duration::days(THRESHOLD_DAYS); let timestamp = &date.timestamp().to_string()[..]; diff --git a/examples/image_from_scratch.rs b/examples/image_from_scratch.rs index 63eb0838..93488769 100644 --- a/examples/image_from_scratch.rs +++ b/examples/image_from_scratch.rs @@ -58,7 +58,7 @@ async fn main() -> Result<(), Box> { let file = File::open(&arguments[1]).expect("Could not find archive."); - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); let options = CreateImageOptions { from_src: "-", // from_src must be "-" when sending the archive in the request body diff --git a/examples/info.rs b/examples/info.rs index c8e8b200..0ac4a968 100644 --- a/examples/info.rs +++ b/examples/info.rs @@ -26,7 +26,7 @@ async fn conc(arg: (Docker, &ContainerSummary)) -> () { #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); let mut list_container_filters = HashMap::new(); list_container_filters.insert("status", vec!["running"]); diff --git a/examples/kafka.rs b/examples/kafka.rs index a9c0ae30..3504936a 100644 --- a/examples/kafka.rs +++ b/examples/kafka.rs @@ -14,7 +14,7 @@ const ZOOKEEPER_IMAGE: &'static str = "confluentinc/cp-zookeeper:5.0.1"; #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); let sd1 = docker.clone(); let sd2 = docker.clone(); diff --git a/examples/post_dockerfile.rs b/examples/post_dockerfile.rs index a9709c71..952bc068 100644 --- a/examples/post_dockerfile.rs +++ b/examples/post_dockerfile.rs @@ -14,7 +14,7 @@ use std::env::args; #[tokio::main] async fn main() { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); let image_options = BuildImageOptions { dockerfile: "Dockerfile", diff --git a/examples/stats.rs b/examples/stats.rs index 19458754..caf1a03d 100644 --- a/examples/stats.rs +++ b/examples/stats.rs @@ -10,7 +10,7 @@ use std::collections::HashMap; #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults().unwrap(); + let docker = Docker::connect_with_socket_defaults().unwrap(); loop { let mut filter = HashMap::new(); diff --git a/examples/top.rs b/examples/top.rs index 1333533c..6fe47b88 100644 --- a/examples/top.rs +++ b/examples/top.rs @@ -12,7 +12,7 @@ use futures_util::stream::StreamExt; #[tokio::main] async fn main() -> Result<(), Box> { - let docker = Docker::connect_with_unix_defaults()?; + let docker = Docker::connect_with_socket_defaults().unwrap(); let mut list_container_filters = HashMap::new(); list_container_filters.insert("status", vec!["running"]); diff --git a/src/docker.rs b/src/docker.rs index 9dc09cb0..84160f63 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -25,7 +25,7 @@ use hyper::{self, body::Bytes, Body, Method, Request, Response, StatusCode}; #[cfg(feature = "ssl")] use hyper_rustls::HttpsConnector; #[cfg(unix)] -use hyperlocal::UnixClient as UnixConnector; +use hyperlocal::UnixConnector; #[cfg(feature = "ssl")] use rustls::internal::pemfile; #[cfg(feature = "ssl")] @@ -525,12 +525,67 @@ impl Docker { Ok(docker) } + + /// Connect using to either a Unix socket or a Windows named pipe using defaults common to the + /// standard docker configuration. + /// + /// # Defaults + /// + /// - The unix socket location defaults to `/var/run/docker.sock`. The windows named pipe + /// location defaults to `//./pipe/docker_engine`. + /// - The request timeout defaults to 2 minutes. + /// + /// # Examples + /// + /// ```rust,no_run + /// use bollard::Docker; + /// + /// use futures_util::future::TryFutureExt; + /// + /// let connection = Docker::connect_with_socket_defaults().unwrap(); + /// connection.ping().map_ok(|_| Ok::<_, ()>(println!("Connected!"))); + /// ``` + pub fn connect_with_socket_defaults() -> Result { + #[cfg(unix)] + let path = DEFAULT_SOCKET; + #[cfg(windows)] + let path = DEFAULT_NAMED_PIPE; + + Docker::connect_with_socket(path, DEFAULT_TIMEOUT, API_DEFAULT_VERSION) + } + + /// Connect using a Unix socket or a Windows named pipe. + /// + /// # Arguments + /// + /// - `path`: connection unix socket path or windows named pipe path. + /// - `timeout`: the read/write timeout (seconds) to use for every hyper connection + /// - `client_version`: the client version to communicate with the server. + /// + /// # Examples + /// + /// ```rust,no_run + /// use bollard::{API_DEFAULT_VERSION, Docker}; + /// + /// use futures_util::future::TryFutureExt; + /// + /// let connection = Docker::connect_with_socket("/var/run/docker.sock", 120, API_DEFAULT_VERSION).unwrap(); + /// connection.ping().map_ok(|_| Ok::<_, ()>(println!("Connected!"))); + /// ``` + pub fn connect_with_socket(path: &str, timeout: u64, client_version: &ClientVersion) -> Result { + #[cfg(unix)] + let docker = Docker::connect_with_unix(path, timeout, client_version); + #[cfg(windows)] + let docker = Docker::connect_with_named_pipe(path, timeout, client_version); + + docker + } } #[cfg(unix)] /// A Docker implementation typed to connect to a Unix socket. impl Docker { - /// Connect using a Unix socket using defaults that are signalled by environment variables. + /// Connect using a Unix socket using defaults common to the standard docker configuration. /// /// # Defaults /// @@ -570,11 +625,11 @@ impl Docker { /// connection.ping().map_ok(|_| Ok::<_, ()>(println!("Connected!"))); /// ``` pub fn connect_with_unix( - addr: &str, + path: &str, timeout: u64, client_version: &ClientVersion, ) -> Result { - let client_addr = addr.replacen("unix://", "", 1); + let client_addr = path.replacen("unix://", "", 1); let unix_connector = UnixConnector; @@ -602,8 +657,8 @@ impl Docker { /// A Docker implementation typed to connect to a Windows Named Pipe, exclusive to the windows /// target. impl Docker { - /// Connect using a Windows Named Pipe using defaults that are signalled by environment - /// variables. + /// Connect using a Windows Named Pipe using defaults that are common to the standard docker + /// configuration. /// /// # Defaults /// @@ -646,11 +701,11 @@ impl Docker { /// /// ``` pub fn connect_with_named_pipe( - addr: &str, + path: &str, timeout: u64, client_version: &ClientVersion, ) -> Result { - let client_addr = addr.replacen("npipe://", "", 1); + let client_addr = path.replacen("npipe://", "", 1); let named_pipe_connector = NamedPipeConnector; @@ -659,7 +714,7 @@ impl Docker { client_builder.http1_title_case_headers(true); let client = client_builder.build(named_pipe_connector); let transport = Transport::NamedPipe { client }; - let _docker = Docker { + let docker = Docker { transport: Arc::new(transport), client_type: ClientType::NamedPipe, client_addr, @@ -670,7 +725,7 @@ impl Docker { )), }; - Err(NamedPipeDisabled {}.into()) + Ok(docker) } } diff --git a/src/errors.rs b/src/errors.rs index 8d8e1333..6bbec0a1 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -128,7 +128,4 @@ pub enum Error { #[from] err: serde_urlencoded::ser::Error, }, - /// Disable NamedPipe support until tokio#3511 is resolved - #[error("Named Pipe support is disabled until tokio#3511 is resolved")] - NamedPipeDisabled {}, } diff --git a/src/lib.rs b/src/lib.rs index eddd8496..019c5262 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,8 +10,8 @@ //! [Tokio](https://github.com/tokio-rs/tokio) improvements for an asynchronous API containing //! futures, streams and the async/await paradigm. //! -//! The library also features Windows support through Named Pipes (disabled in 0.10, see below) and -//! HTTPS support through optional rustls bindings. +//! The library also features Windows support through Named Pipes and HTTPS support through +//! optional rustls bindings. //! //! # Install //! @@ -19,7 +19,7 @@ //! //! ```nocompile //! [dependencies] -//! bollard = "0.9" +//! bollard = "0.11" //! ``` //! //! # API @@ -27,18 +27,16 @@ //! //! [API docs](crate). //! -//! Version 0.10 disables Named Pipe Windows support until the upstream Tokio project re-adds -//! support for Named Pipes. Please follow the [tracking -//! issue](https://github.com/tokio-rs/tokio/issues/3511) for updates on this. -//! +//! Version 0.11 re-enables Windows Named Pipe support. +//! //! As of version 0.6, this project now generates API stubs from the upstream Docker-maintained -//! [Swagger OpenAPI specification](https://docs.docker.com/engine/api/v1.40.yaml). The generated +//! [Swagger OpenAPI specification](https://docs.docker.com/engine/api/v1.41.yaml). The generated //! models are committed to this repository, but packaged in a separate crate //! [bollard-stubs](https://crates.io/crates/bollard-stubs). //! //! ## Version //! -//! The [Docker API](https://docs.docker.com/engine/api/v1.40/) is pegged at version `1.40`. The +//! The [Docker API](https://docs.docker.com/engine/api/v1.41/) is pegged at version `1.41`. The //! library also supports [version //! negotiation](https://docs.rs/bollard/latest/bollard/struct.Docker.html#method.negotiate_version), //! to allow downgrading to an older API version. @@ -49,15 +47,16 @@ //! //! Connect to the docker server according to your architecture and security remit. //! -//! ### Unix socket +//! ### Socket //! -//! The client will connect to the standard unix socket location `/var/run/docker.sock`. Use the -//! `Docker::connect_with_unix` method API to parameterise the interface. +//! The client will connect to the standard unix socket location `/var/run/docker.sock` or windows +//! named pipe location `//./pipe/docker_engine`. Use the `Docker::connect_with_socket` method API +//! to parameterise the interface. //! //! ```rust //! use bollard::Docker; //! #[cfg(unix)] -//! Docker::connect_with_unix_defaults(); +//! Docker::connect_with_socket_defaults(); //! ``` //! //! ### Local diff --git a/src/named_pipe.rs b/src/named_pipe.rs index e0ad444c..d2e65fce 100644 --- a/src/named_pipe.rs +++ b/src/named_pipe.rs @@ -3,69 +3,84 @@ use hyper::client::connect::Connected; use pin_project::pin_project; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::net::windows::named_pipe::{ClientOptions, NamedPipeClient}; +use tokio::time; -use std::fmt; +use std::ffi::OsStr; use std::future::Future; use std::io; use std::path::Path; use std::pin::Pin; use std::task::{Context, Poll}; +use std::time::Duration; + +use winapi::shared::winerror; use crate::docker::ClientType; use crate::uri::Uri; -#[derive(Debug)] -struct NamedPipe {} + #[pin_project] pub struct NamedPipeStream { #[pin] - io: NamedPipe + io: NamedPipeClient } impl NamedPipeStream { pub async fn connect(addr: A) -> Result where - A: AsRef, + A: AsRef + AsRef, { - let io = NamedPipe {}; + let opts = ClientOptions::new(); + + let client = loop { + match opts.open(&addr) { + Ok(client) => break client, + Err(e) if e.raw_os_error() == Some(winerror::ERROR_PIPE_BUSY as i32) => (), + Err(e) => return Err(e), + }; - Ok(NamedPipeStream { io }) + time::sleep(Duration::from_millis(50)).await; + }; + + Ok(NamedPipeStream{ io: client }) } } impl AsyncRead for NamedPipeStream { - fn poll_read( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, - ) -> Poll> { - Poll::Ready(Ok(())) + ) -> Poll> { + Pin::new(&mut self.io).poll_read(cx, buf) } } impl AsyncWrite for NamedPipeStream { - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.io).poll_write(cx, buf) } - fn poll_write( - self: Pin<&mut Self>, + fn poll_write_vectored( + mut self: Pin<&mut Self>, cx: &mut Context<'_>, - bytes: &[u8], - ) -> Poll> { - Poll::Ready(Ok(0)) + bufs: &[io::IoSlice<'_>], + ) -> Poll> { + Pin::new(&mut self.io).poll_write_vectored(cx, bufs) } - fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } -} -impl fmt::Debug for NamedPipeStream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.io.fmt(f) + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) } }