Skip to content
Open
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ jobs:
- name: Test
run: |
docker pull ubuntu:latest
cargo test --all-features --all-targets
cargo test --all-features --all-targets -j1
cargo test --doc
2 changes: 1 addition & 1 deletion examples/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
match opts.subcmd {
Cmd::Attach { id } => {
let container = docker.containers().get(&id);
let tty_multiplexer = container.attach().await?;
let tty_multiplexer = container.attach(false).await?;

let (mut reader, _writer) = tty_multiplexer.split();

Expand Down
23 changes: 21 additions & 2 deletions src/api/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,18 @@ impl Container {
/// The [`TtyMultiplexer`](TtyMultiplexer) implements Stream for returning Stdout and Stderr chunks. It also implements [`AsyncWrite`](futures_util::io::AsyncWrite) for writing to Stdin.
///
/// The multiplexer can be split into its read and write halves with the [`split`](TtyMultiplexer::split) method
pub async fn attach(&self) -> Result<tty::Multiplexer> {
pub async fn attach(&self, logs: bool) -> Result<tty::Multiplexer> {
let inspect = self.inspect().await?;
let logs = if logs {
1
} else {
0
};
let is_tty = inspect.config.and_then(|c| c.tty).unwrap_or_default();
stream::attach(
self.docker.clone(),
format!(
"/containers/{}/attach?stream=1&stdout=1&stderr=1&stdin=1",
"/containers/{}/attach?stream=1&stdout=1&stderr=1&stdin=1&logs={logs}",
self.id
),
Payload::empty(),
Expand Down Expand Up @@ -171,6 +176,20 @@ impl Container {
.map(|_| ())
}}

api_doc! { Container => Resize
|
/// Resize the TTY for a container
pub async fn resize(&self, width: u16, height: u16) -> Result<()> {
self.docker
.post_string(
&format!("/containers/{}/resize?w={width}&h={height}", self.id),
Payload::empty(),
Headers::none()
)
.await
.map(|_| ())
}}

api_doc! { Container => Pause
|
/// Pause the container instance.
Expand Down
2 changes: 1 addition & 1 deletion src/api/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl Image {
let headers = opts
.auth_header()
.map(|auth| Headers::single(AUTH_HEADER, auth))
.unwrap_or_else(Headers::default);
.unwrap_or_default();

self.docker
.post_string(&ep, Payload::empty(), Some(headers))
Expand Down
2 changes: 1 addition & 1 deletion src/api/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub mod swarm;
#[cfg_attr(docsrs, doc(cfg(feature = "swarm")))]
pub mod task;

pub use {container::*, exec::*, image::*, network::*, system::*, volume::*};
pub use {container::*, exec::*, image::*, network::*, volume::*};

#[cfg(feature = "swarm")]
#[cfg_attr(docsrs, doc(cfg(feature = "swarm")))]
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ mod stream;
pub mod conn {
//! Connection related items
pub(crate) use containers_api::conn::*;
pub use containers_api::conn::{Error, Transport, TtyChunk};
pub use containers_api::conn::{
tty::Multiplexer as TtyMultiplexer, Error, Transport, TtyChunk,
};
}
pub mod docker;
pub mod errors;
Expand Down
37 changes: 26 additions & 11 deletions src/opts/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use containers_api::{
impl_str_field, impl_url_bool_field, impl_url_str_field, impl_vec_field,
};

use std::fmt::{Display, Formatter};
use std::net::SocketAddr;
use std::{
collections::HashMap,
Expand Down Expand Up @@ -331,9 +332,9 @@ impl FromStr for PublishPort {
}
}

impl ToString for PublishPort {
fn to_string(&self) -> String {
format!("{}/{}", self.port, self.protocol.as_ref())
impl Display for PublishPort {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}/{}", self.port, self.protocol.as_ref())
}
}

Expand Down Expand Up @@ -388,15 +389,16 @@ pub enum IpcMode {
Host,
}

impl ToString for IpcMode {
fn to_string(&self) -> String {
match &self {
impl Display for IpcMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match &self {
IpcMode::None => String::from("none"),
IpcMode::Private => String::from("private"),
IpcMode::Shareable => String::from("shareable"),
IpcMode::Container(id) => format!("container:{}", id),
IpcMode::Host => String::from("host"),
}
};
write!(f, "{}", str)
}
}

Expand All @@ -408,12 +410,13 @@ pub enum PidMode {
Host,
}

impl ToString for PidMode {
fn to_string(&self) -> String {
match &self {
impl Display for PidMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match &self {
PidMode::Container(id) => format!("container:{}", id),
PidMode::Host => String::from("host"),
}
};
write!(f, "{}", str)
}
}

Expand Down Expand Up @@ -524,6 +527,11 @@ impl ContainerCreateOptsBuilder {
security_options => "HostConfig.SecurityOpt"
);

impl_field!(
/// Mount the container's root filesystem as read only.
readonly_rootfs: bool => "HostConfig.ReadonlyRootfs"
);

impl_vec_field!(
/// Specify any bind mounts, taking the form of `/some/host/path:/some/container/path`
volumes => "HostConfig.Binds"
Expand Down Expand Up @@ -859,6 +867,13 @@ mod tests {
r#"{"HostConfig":{"AutoRemove":true,"NetworkMode":"host","Privileged":true},"Image":"test_image"}"#
);

test_case!(
ContainerCreateOptsBuilder::default()
.image("test_image")
.readonly_rootfs(true),
r#"{"HostConfig":{"ReadonlyRootfs":true},"Image":"test_image"}"#
);

test_case!(
ContainerCreateOptsBuilder::default()
.image("test_image")
Expand Down
10 changes: 6 additions & 4 deletions src/opts/image.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use std::fmt::Display;
use std::{
collections::HashMap,
path::{Path, PathBuf},
Expand Down Expand Up @@ -370,16 +371,17 @@ pub enum ImageName {
Digest { image: String, digest: String },
}

impl ToString for ImageName {
fn to_string(&self) -> String {
match &self {
impl Display for ImageName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let str = match &self {
ImageName::Tag { image, tag } => match tag {
Some(tag) => format!("{image}:{tag}"),
None => image.to_owned(),
},
ImageName::Id(id) => id.to_owned(),
ImageName::Digest { image, digest } => format!("{image}@{digest}"),
}
};
write!(f, "{}", str)
}
}

Expand Down
10 changes: 6 additions & 4 deletions src/opts/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,16 +129,15 @@ mod tests {
#[cfg(feature = "chrono")]
#[test]
fn logs_options() {
let timestamp = chrono::NaiveDateTime::from_timestamp_opt(2_147_483_647, 0);
let since = chrono::DateTime::<chrono::Utc>::from_utc(timestamp.unwrap(), chrono::Utc);
let timestamp = chrono::DateTime::from_timestamp(2_147_483_647, 0).unwrap();

let options = LogsOptsBuilder::default()
.follow(true)
.stdout(true)
.stderr(true)
.timestamps(true)
.all()
.since(&since)
.since(&timestamp)
.build();

let serialized = options.serialize().unwrap();
Expand All @@ -150,7 +149,10 @@ mod tests {
assert!(serialized.contains("tail=all"));
assert!(serialized.contains("since=2147483647"));

let options = LogsOptsBuilder::default().n_lines(5).until(&since).build();
let options = LogsOptsBuilder::default()
.n_lines(5)
.until(&timestamp)
.build();

let serialized = options.serialize().unwrap();

Expand Down
8 changes: 6 additions & 2 deletions tests/common.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
use std::env;
use std::path::PathBuf;

pub use docker_api::{api, conn, models, models::ImageBuildChunk, opts, Docker};
pub use futures_util::{StreamExt, TryStreamExt};
#[cfg(test)]
pub use docker_api::conn;
pub use docker_api::{api, models, models::ImageBuildChunk, opts, Docker};
pub use futures_util::StreamExt;
#[cfg(test)]
pub use futures_util::TryStreamExt;
pub use tempfile::TempDir;

pub const DEFAULT_IMAGE: &str = "ubuntu:latest";
Expand Down
27 changes: 20 additions & 7 deletions tests/container_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,14 +501,29 @@ async fn container_stats() {
async fn container_top() {
let docker = init_runtime();

let version = docker.version().await.unwrap();
let is_podman = version
.components
.unwrap_or_default()
.iter()
.any(|component| component.name == "Podman Engine");

let container_name = "test-top-container";
let container = create_base_container(&docker, container_name, None).await;

let _ = container.start().await;

let top_result = container.top(None).await;
assert!(top_result.is_ok());
assert!(top_result.unwrap().processes.unwrap_or_default()[0].contains(&DEFAULT_CMD.to_string()));

if is_podman {
assert!(top_result.unwrap().processes.unwrap_or_default()[0][0]
.contains(&DEFAULT_CMD.to_string()));
} else {
assert!(
top_result.unwrap().processes.unwrap_or_default()[0].contains(&DEFAULT_CMD.to_string())
);
}

cleanup_container(&docker, container_name).await;
}
Expand Down Expand Up @@ -676,13 +691,12 @@ async fn container_attach() {

let _ = container.start().await;

let mut multiplexer = container.attach().await.unwrap();
while let Some(chunk) = multiplexer.next().await {
let mut multiplexer = container.attach(false).await.unwrap();
if let Some(chunk) = multiplexer.next().await {
match chunk {
Ok(TtyChunk::StdOut(chunk)) => {
let logs = String::from_utf8_lossy(&chunk);
assert_eq!(logs, "123456\r\n");
break;
}
chunk => {
eprintln!("invalid chunk {chunk:?}");
Expand Down Expand Up @@ -712,13 +726,12 @@ async fn container_attach() {

let _ = container.start().await;

let mut multiplexer = container.attach().await.unwrap();
while let Some(chunk) = multiplexer.next().await {
let mut multiplexer = container.attach(false).await.unwrap();
if let Some(chunk) = multiplexer.next().await {
match chunk {
Ok(TtyChunk::StdOut(chunk)) => {
let logs = String::from_utf8_lossy(&chunk);
assert_eq!(logs, "123456\n");
break;
}
chunk => {
eprintln!("invalid chunk {chunk:?}");
Expand Down
5 changes: 0 additions & 5 deletions tests/docker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,6 @@ async fn docker_info() {

let info_result = docker.info().await;
assert!(info_result.is_ok());
let info_data = info_result.unwrap();
assert_eq!(
info_data.name.unwrap(),
gethostname::gethostname().into_string().unwrap()
);
}

#[tokio::test]
Expand Down
12 changes: 8 additions & 4 deletions tests/image_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ mod common;

use common::{
create_base_image, get_image_full_id, init_runtime, opts, tempdir_with_dockerfile, StreamExt,
TryStreamExt, DEFAULT_IMAGE,
DEFAULT_IMAGE,
};

use futures_util::TryStreamExt;

#[tokio::test]
async fn image_create_inspect_delete() {
let docker = init_runtime();
Expand Down Expand Up @@ -41,7 +43,8 @@ async fn image_inspect() {
.repo_tags
.as_ref()
.unwrap()
.contains(&format!("{image_name}:latest")));
.iter()
.any(|tag| tag.contains(&format!("{image_name}:latest"))));
assert!(image.delete().await.is_ok());
}

Expand All @@ -61,7 +64,7 @@ async fn image_history() {
println!("{history_data:#?}");
assert!(history_data
.iter()
.any(|item| item.tags.iter().any(|t| t == DEFAULT_IMAGE)));
.any(|item| item.tags.iter().any(|t| t.contains(DEFAULT_IMAGE))));
}

#[tokio::test]
Expand Down Expand Up @@ -89,7 +92,8 @@ async fn image_tag() {
.expect("image inspect data")
.repo_tags
.expect("repo tags")
.contains(&new_tag));
.iter()
.any(|tag| tag.contains(&new_tag)));

//cleanup
let _ = image.delete().await;
Expand Down
3 changes: 1 addition & 2 deletions tests/network_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,7 @@ async fn network_connect_disconnect() {
.unwrap()
.networks
.unwrap()
.get(network_name)
.is_some());
.contains_key(network_name));

let _ = network.delete().await;
let _ = container.delete().await;
Expand Down