mod common;
use common::{
cleanup_container,
conn::TtyChunk,
create_base_container, get_container_full_id, init_runtime,
opts::{ContainerCreateOpts, ExecCreateOpts},
StreamExt, TryStreamExt, DEFAULT_CMD, DEFAULT_CMD_ARRAY, DEFAULT_IMAGE,
};
#[tokio::test]
async fn container_create_inspect_remove() {
let docker = init_runtime();
let container_name = "test-create-exist-container";
let container = create_base_container(&docker, container_name, None).await;
let inspect_result = container.inspect().await;
assert!(inspect_result.is_ok());
let remove_result = container.delete().await;
assert!(remove_result.is_ok());
let inspect_result = container.inspect().await;
assert!(inspect_result.is_err());
}
#[tokio::test]
async fn container_inspect() {
let docker = init_runtime();
let container_name = "test-inspect-container";
let container = create_base_container(&docker, container_name, None).await;
let inspect_result = container.inspect().await;
assert!(inspect_result.is_ok());
let inspect_data = inspect_result.unwrap();
assert!(inspect_data
.config
.as_ref()
.unwrap()
.image
.as_ref()
.unwrap()
.contains("ubuntu:latest"));
assert!(inspect_data.name.as_ref().unwrap().contains(container_name));
assert_eq!(inspect_data.config.unwrap().cmd.unwrap(), DEFAULT_CMD_ARRAY);
cleanup_container(&docker, container_name).await;
let inspect_result = container.inspect().await;
assert!(inspect_result.is_err());
}
#[tokio::test]
async fn container_rename() {
let docker = init_runtime();
cleanup_container(&docker, "new-container-name").await;
let container_name = "test-rename-container";
let new_container_name = "test-rename-container-new";
let container = create_base_container(&docker, container_name, None).await;
let inspect_result = docker.containers().get(new_container_name).inspect().await;
assert!(inspect_result.is_err());
let rename_result = container.rename(new_container_name).await;
assert!(rename_result.is_ok());
let inspect_result = docker.containers().get(new_container_name).inspect().await;
assert!(inspect_result.is_ok());
cleanup_container(&docker, container_name).await;
cleanup_container(&docker, new_container_name).await;
}
#[tokio::test]
async fn container_start() {
let docker = init_runtime();
let container_name = "test-start-container";
let container = create_base_container(&docker, container_name, None).await;
let start_result = container.start().await;
assert!(start_result.is_ok());
let inspect_data = container
.inspect()
.await
.expect("started container inspect data");
assert!(inspect_data.state.unwrap().running.unwrap());
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_restart() {
let docker = init_runtime();
let container_name = "test-restart-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let timestamp = container
.inspect()
.await
.expect("restarted container inspect data before")
.state
.unwrap()
.started_at
.unwrap();
let restart_result = container.restart(&Default::default()).await;
assert!(restart_result.is_ok());
let timestamp_after = container
.inspect()
.await
.expect("restarted container inspect data")
.state
.unwrap()
.started_at
.unwrap();
assert_ne!(timestamp, timestamp_after);
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_pause_unpause() {
let docker = init_runtime();
let container_name = "test-pause-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let pause_result = container.pause().await;
assert!(pause_result.is_ok());
let inspect_data = container
.inspect()
.await
.expect("paused container inspect data");
assert!(inspect_data.state.unwrap().paused.unwrap());
let unpause_result = container.unpause().await;
assert!(unpause_result.is_ok());
let inspect_data = container
.inspect()
.await
.expect("paused container inspect data");
assert!(!inspect_data.state.unwrap().paused.unwrap());
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_kill() {
let docker = init_runtime();
let container_name = "test-kill-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let inspect_data = container
.inspect()
.await
.expect("killed container inspect data");
let state = inspect_data.state.unwrap();
assert!(state.running.unwrap());
let kill_result = container.kill(None).await;
assert!(kill_result.is_ok());
let inspect_data = container
.inspect()
.await
.expect("killed container inspect data");
let state = inspect_data.state.unwrap();
assert!(!state.running.unwrap());
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_stop() {
let docker = init_runtime();
let container_name = "test-stop-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let inspect_data = container
.inspect()
.await
.expect("stopped container inspect data");
assert!(inspect_data.state.unwrap().running.unwrap());
let stop_result = container.stop(&Default::default()).await;
assert!(stop_result.is_ok());
let inspect_data = container
.inspect()
.await
.expect("stopped container inspect data");
assert!(!inspect_data.state.unwrap().running.unwrap());
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_commit() {
use docker_api::opts::ContainerCommitOpts;
let docker = init_runtime();
let container_name = "test-commit-container";
let container = create_base_container(&docker, container_name, None).await;
let author = "docker api rs";
let commit_result = container
.commit(
&ContainerCommitOpts::builder()
.author(author)
.repo("test-commit-image")
.tag("test")
.build(),
None,
)
.await;
assert!(commit_result.is_ok());
let image_name = "test-commit-image:test";
let inspect_data = docker
.images()
.get(image_name)
.inspect()
.await
.expect("commited image inspect data");
assert_eq!(inspect_data.author.unwrap(), author);
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_exec() {
let docker = init_runtime();
let container_name = "test-exec-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let mut exec_stream = container
.exec(
&ExecCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.command([
"bash",
"-c",
"mkdir /tmp/test123 && echo 1234 >> /tmp/test123/testfile",
])
.build(),
&Default::default(),
)
.await
.unwrap();
while exec_stream.next().await.is_some() {}
let mut exec_stream = container
.exec(
&ExecCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.command(["cat", "test123/testfile"])
.working_dir("/tmp")
.build(),
&Default::default(),
)
.await
.unwrap();
let chunk = exec_stream.next().await;
assert!(chunk.is_some());
match chunk.unwrap() {
Ok(TtyChunk::StdOut(chunk)) => {
let testfile_content = String::from_utf8_lossy(&chunk);
assert_eq!(testfile_content, "1234\n");
}
Ok(chunk) => {
let fd = match chunk {
TtyChunk::StdIn(_) => "stdin",
TtyChunk::StdOut(_) => "stdOut",
TtyChunk::StdErr(_) => "stderr",
};
let chunk = String::from_utf8_lossy(&chunk);
eprintln!("invalid chunk, fd: {fd}, content: `{chunk:?}`");
std::process::exit(1);
}
chunk => {
eprintln!("invalid chunk {chunk:?}");
std::process::exit(1);
}
}
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_copy_from() {
let docker = init_runtime();
let container_name = "test-copy-from-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let mut exec_stream = container
.exec(
&ExecCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.command([
"bash",
"-c",
"mkdir /tmp/test123 && echo 1234 >> /tmp/test123/test-copy-from",
])
.build(),
&Default::default(),
)
.await
.unwrap();
while exec_stream.next().await.is_some() {}
let tar_stream = container.copy_from("/tmp/test123");
let bytes = tar_stream.try_concat().await.expect("joined tarball bytes");
let mut archive = tar::Archive::new(&bytes[..]);
let tmp = tempfile::TempDir::new().expect("temporary dir");
archive.unpack(tmp.path()).unwrap();
let local_path = tmp.path().join("test123").join("test-copy-from");
assert!(local_path.exists());
assert_eq!(std::fs::read_to_string(&local_path).unwrap(), "1234\n");
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_copy_file_into() {
let docker = init_runtime();
let container_name = "test-copy-file-into-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let data = b"12345";
let copy_result = container.copy_file_into("/tmp/test-file", data).await;
assert!(copy_result.is_ok());
let mut exec_stream = container
.exec(
&ExecCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.command(["cat", "/tmp/test-file"])
.build(),
&Default::default(),
)
.await
.unwrap();
let chunk = exec_stream.next().await;
assert!(chunk.is_some());
match chunk.unwrap() {
Ok(TtyChunk::StdOut(chunk)) => {
assert_eq!(chunk, data);
}
chunk => {
eprintln!("invalid chunk {chunk:?}");
std::process::exit(1);
}
}
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_changes() {
let docker = init_runtime();
let container_name = "test-changes-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
let mut exec_stream = container
.exec(
&ExecCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.command([
"bash",
"-c",
"rm /etc/xattr.conf && echo 12345 >> /tmp/test-changes",
])
.build(),
&Default::default(),
)
.await
.unwrap();
while exec_stream.next().await.is_some() {}
use docker_api::models::ContainerChangeResponseItem;
let changes = container
.changes()
.await
.expect("container changes")
.unwrap_or_default();
assert!(changes.contains(&ContainerChangeResponseItem {
kind: 0,
path: "/tmp".into()
}));
assert!(changes.contains(&ContainerChangeResponseItem {
kind: 1,
path: "/tmp/test-changes".into()
}));
assert!(changes.contains(&ContainerChangeResponseItem {
kind: 2,
path: "/etc/xattr.conf".into()
}));
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_logs() {
let docker = init_runtime();
let container_name = "test-logs-container";
let container = create_base_container(
&docker,
container_name,
Some(
ContainerCreateOpts::builder()
.image(DEFAULT_IMAGE)
.name(container_name)
.command(["bash", "-c", "echo 123456 && sleep inf"])
.build(),
),
)
.await;
let _ = container.start().await;
use docker_api::opts::LogsOpts;
let mut logs_stream = container.logs(&LogsOpts::builder().stdout(true).stderr(true).build());
let chunk = logs_stream.next().await;
match chunk {
Some(Ok(TtyChunk::StdOut(chunk))) => {
let logs = String::from_utf8_lossy(&chunk);
assert_eq!(logs, "123456\n");
}
chunk => {
eprintln!("invalid chunk {chunk:?}");
std::process::exit(1);
}
}
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_stats() {
let docker = init_runtime();
let container_name = "test-stats-container";
let container = create_base_container(&docker, container_name, None).await;
let _ = container.start().await;
const WANT: i32 = 5;
let mut got = 0;
let mut stats_stream = container.stats();
for _ in 0..WANT {
if let Some(chunk) = stats_stream.next().await {
assert!(chunk.is_ok());
got += 1;
}
}
assert_eq!(got, WANT);
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn container_top() {
let docker = init_runtime();
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()));
cleanup_container(&docker, container_name).await;
}
#[tokio::test]
async fn containers_list() {
use docker_api::opts::{ContainerFilter, ContainerListOpts};
let docker = init_runtime();
let container_name = "test-list-container";
let opts = ContainerCreateOpts::builder()
.command(DEFAULT_CMD_ARRAY)
.image(DEFAULT_IMAGE)
.labels([("test-docker-list", "value")])
.name(container_name)
.build();
let second_name = "test-list-second-container";
let second_opts = ContainerCreateOpts::builder()
.command(DEFAULT_CMD_ARRAY)
.image(DEFAULT_IMAGE)
.labels([("test-docker-list", "value2")])
.name(second_name)
.build();
create_base_container(&docker, container_name, Some(opts)).await;
create_base_container(&docker, second_name, Some(second_opts)).await;
let filter = ContainerFilter::Name(container_name.to_string());
let opts = ContainerListOpts::builder()
.all(true)
.filter([filter])
.build();
let list_result = docker.containers().list(&opts).await;
assert!(list_result.is_ok());
let list_data = list_result.unwrap();
assert_eq!(list_data.len(), 1);
assert!(list_data[0]
.names
.as_ref()
.unwrap()
.contains(&format!("/{container_name}")));
let filter = ContainerFilter::LabelKey("test-docker-list".into());
let opts = ContainerListOpts::builder()
.all(true)
.filter([filter])
.build();
let list_result = docker.containers().list(&opts).await;
assert!(list_result.is_ok());
let list_data = list_result.unwrap();
assert_eq!(list_data.len(), 2);
let filter = ContainerFilter::Label("test-docker-list".into(), "value".into());
let opts = ContainerListOpts::builder()
.all(true)
.filter([filter])
.build();
let list_result = docker.containers().list(&opts).await;
assert!(list_result.is_ok());
let list_data = list_result.unwrap();
assert_eq!(list_data.len(), 1);
assert!(list_data[0]
.names
.as_ref()
.unwrap()
.contains(&format!("/{container_name}")));
let filter = ContainerFilter::Label("test-docker-list".into(), "value2".into());
let opts = ContainerListOpts::builder()
.all(true)
.filter([filter])
.build();
let list_result = docker.containers().list(&opts).await;
assert!(list_result.is_ok());
let list_data = list_result.unwrap();
assert_eq!(list_data.len(), 1);
assert!(list_data[0]
.names
.as_ref()
.unwrap()
.contains(&format!("/{second_name}")));
cleanup_container(&docker, container_name).await;
cleanup_container(&docker, second_name).await;
}
#[tokio::test]
async fn containers_prune() {
use docker_api::opts::{ContainerPruneFilter, ContainerPruneOpts};
let docker = init_runtime();
let container_name = "test-prune-container";
let opts = ContainerCreateOpts::builder()
.command(DEFAULT_CMD_ARRAY)
.image(DEFAULT_IMAGE)
.labels([("test-docker-prune", "value")])
.name(container_name)
.build();
let second_name = "test-prune-second-container";
let second_opts = ContainerCreateOpts::builder()
.command(DEFAULT_CMD_ARRAY)
.image(DEFAULT_IMAGE)
.labels([("test-docker-prune", "value2")])
.name(second_name)
.build();
create_base_container(&docker, container_name, Some(opts.clone())).await;
create_base_container(&docker, second_name, Some(second_opts.clone())).await;
let full_id = get_container_full_id(&docker, container_name).await;
let second_full_id = get_container_full_id(&docker, second_name).await;
let filter = ContainerPruneFilter::LabelKey("test-docker-prune".into());
let prune_opts = ContainerPruneOpts::builder().filter([filter]).build();
let prune_result = docker.containers().prune(&prune_opts).await;
assert!(prune_result.is_ok());
let prune_data = prune_result.unwrap().containers_deleted.unwrap_or_default();
assert_eq!(prune_data.len(), 2);
assert!(prune_data.iter().any(|id| id == &full_id));
assert!(prune_data.iter().any(|id| id == &second_full_id));
create_base_container(&docker, container_name, Some(opts.clone())).await;
create_base_container(&docker, second_name, Some(second_opts.clone())).await;
let full_id = get_container_full_id(&docker, container_name).await;
let second_full_id = get_container_full_id(&docker, second_name).await;
let filter = ContainerPruneFilter::Label("test-docker-prune".into(), "value".into());
let prune_opts = ContainerPruneOpts::builder().filter([filter]).build();
let prune_result = docker.containers().prune(&prune_opts).await;
assert!(prune_result.is_ok());
let prune_data = prune_result.unwrap().containers_deleted.unwrap_or_default();
assert_eq!(prune_data.len(), 1);
assert!(prune_data.iter().any(|id| id == &full_id));
let filter = ContainerPruneFilter::Label("test-docker-prune".into(), "value2".into());
let prune_opts = ContainerPruneOpts::builder().filter([filter]).build();
let prune_result = docker.containers().prune(&prune_opts).await;
assert!(prune_result.is_ok());
let prune_data = prune_result.unwrap().containers_deleted.unwrap_or_default();
assert_eq!(prune_data.len(), 1);
assert!(prune_data.iter().any(|id| id == &second_full_id));
}
#[tokio::test]
async fn container_attach() {
let docker = init_runtime();
let container_name = "test-attach-tty-container";
let container = create_base_container(
&docker,
container_name,
Some(
ContainerCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.attach_stdin(true)
.tty(true)
.image(DEFAULT_IMAGE)
.name(container_name)
.command(["bash", "-c", "while true; do echo 123456 && sleep 2; done"])
.build(),
),
)
.await;
let _ = container.start().await;
let mut multiplexer = container.attach().await.unwrap();
while 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:?}");
std::process::exit(1);
}
}
}
cleanup_container(&docker, container_name).await;
let container_name = "test-attach-non-tty-container";
let container = create_base_container(
&docker,
container_name,
Some(
ContainerCreateOpts::builder()
.attach_stderr(true)
.attach_stdout(true)
.attach_stdin(true)
.image(DEFAULT_IMAGE)
.name(container_name)
.command(["bash", "-c", "while true; do echo 123456 && sleep 2; done"])
.build(),
),
)
.await;
let _ = container.start().await;
let mut multiplexer = container.attach().await.unwrap();
while 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:?}");
std::process::exit(1);
}
}
}
cleanup_container(&docker, container_name).await;
}