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; // check that the container got correctly removed 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 stackify_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 stackify_docker_api::models::FilesystemChange; let changes = container .changes() .await .expect("container changes") .unwrap_or_default(); assert!(changes.contains(&FilesystemChange { kind: 0, path: "/tmp".into() })); assert!(changes.contains(&FilesystemChange { kind: 1, path: "/tmp/test-changes".into() })); assert!(changes.contains(&FilesystemChange { 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 stackify_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 stackify_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 stackify_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; }