docker_wrapper/lib.rs
1//! # docker-wrapper
2//!
3//! A comprehensive, type-safe Docker CLI wrapper for Rust applications.
4//!
5//! `docker-wrapper` provides a clean, idiomatic Rust interface to Docker's command-line interface,
6//! supporting all major Docker commands with strong typing, async/await support, and a consistent
7//! builder pattern API.
8//!
9//! ## Features
10//!
11//! - **Complete Docker CLI coverage**: Implements all 35 essential Docker commands
12//! - **Type-safe builder pattern**: Compile-time validation of command construction
13//! - **Async/await support**: Built on Tokio for efficient async operations
14//! - **Streaming support**: Real-time output streaming for long-running commands
15//! - **Docker Compose support**: Optional feature for multi-container orchestration
16//! - **Container templates**: Pre-configured templates for Redis, `PostgreSQL`, `MongoDB`, etc.
17//! - **Zero dependencies on Docker SDK**: Works directly with the Docker CLI
18//! - **Comprehensive error handling**: Detailed error messages and types
19//! - **Well-tested**: Extensive unit and integration test coverage
20//!
21//! ## Quick Start
22//!
23//! Add `docker-wrapper` to your `Cargo.toml`:
24//!
25//! ```toml
26//! [dependencies]
27//! docker-wrapper = "0.2"
28//! tokio = { version = "1", features = ["full"] }
29//! ```
30//!
31//! For Docker Compose support, enable the `compose` feature:
32//!
33//! ```toml
34//! [dependencies]
35//! docker-wrapper = { version = "0.2", features = ["compose"] }
36//! ```
37//!
38//! ## Basic Usage
39//!
40//! ### Running a Container
41//!
42//! ```rust,no_run
43//! use docker_wrapper::{DockerCommand, RunCommand};
44//!
45//! # #[tokio::main]
46//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
47//! // Run a simple container
48//! let output = RunCommand::new("nginx:latest")
49//! .name("my-web-server")
50//! .port(8080, 80)
51//! .detach()
52//! .execute()
53//! .await?;
54//!
55//! println!("Container started: {}", output.0);
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! ### Building an Image
61//!
62//! ```rust,no_run
63//! use docker_wrapper::{DockerCommand, BuildCommand};
64//!
65//! # #[tokio::main]
66//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
67//! let output = BuildCommand::new(".")
68//! .tag("my-app:latest")
69//! .file("Dockerfile")
70//! .build_arg("VERSION", "1.0.0")
71//! .execute()
72//! .await?;
73//!
74//! if let Some(image_id) = &output.image_id {
75//! println!("Built image: {}", image_id);
76//! }
77//! # Ok(())
78//! # }
79//! ```
80//!
81//! ### Listing Containers
82//!
83//! ```rust,no_run
84//! use docker_wrapper::{DockerCommand, PsCommand};
85//!
86//! # #[tokio::main]
87//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
88//! let output = PsCommand::new()
89//! .all()
90//! .format_json()
91//! .execute()
92//! .await?;
93//!
94//! for container in output.containers {
95//! println!("{}: {}", container.names, container.status);
96//! }
97//! # Ok(())
98//! # }
99//! ```
100//!
101//! ## Streaming Output
102//!
103//! For long-running commands, use the streaming API to process output in real-time:
104//!
105//! ```rust,no_run
106//! use docker_wrapper::{BuildCommand, StreamHandler, StreamableCommand};
107//!
108//! # #[tokio::main]
109//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
110//! // Stream build output to console
111//! let result = BuildCommand::new(".")
112//! .tag("my-app:latest")
113//! .stream(StreamHandler::print())
114//! .await?;
115//!
116//! if result.is_success() {
117//! println!("Build completed successfully!");
118//! }
119//! # Ok(())
120//! # }
121//! ```
122//!
123//! ## Container Templates
124//!
125//! Use pre-configured templates for common services:
126//!
127//! ```rust,no_run
128//! # #[cfg(feature = "template-redis")]
129//! use docker_wrapper::{RedisTemplate, Template};
130//!
131//! # #[cfg(feature = "template-redis")]
132//! # #[tokio::main]
133//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
134//! // Start Redis with persistence and custom configuration
135//! let redis = RedisTemplate::new("my-redis")
136//! .port(6379)
137//! .password("secret")
138//! .memory_limit("256m")
139//! .with_persistence("redis-data")
140//! .custom_image("redis", "7-alpine");
141//!
142//! let container_id = redis.start().await?;
143//! println!("Redis started: {}", container_id);
144//!
145//! // Clean up
146//! redis.stop().await?;
147//! # Ok(())
148//! # }
149//! # #[cfg(not(feature = "template-redis"))]
150//! # fn main() {}
151//! ```
152//!
153//! ## Docker Compose Support
154//!
155//! When the `compose` feature is enabled, you can manage multi-container applications:
156//!
157//! ```rust,no_run
158//! # #[cfg(feature = "compose")]
159//! use docker_wrapper::compose::{ComposeCommand, ComposeUpCommand, ComposeDownCommand};
160//!
161//! # #[cfg(feature = "compose")]
162//! # #[tokio::main]
163//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
164//! // Start services defined in docker-compose.yml
165//! ComposeUpCommand::new()
166//! .file("docker-compose.yml")
167//! .detach()
168//! .execute()
169//! .await?;
170//!
171//! // Stop and remove services
172//! ComposeDownCommand::new()
173//! .volumes()
174//! .execute()
175//! .await?;
176//! # Ok(())
177//! # }
178//! # #[cfg(not(feature = "compose"))]
179//! # fn main() {}
180//! ```
181//!
182//! ## Architecture
183//!
184//! The crate is organized around several key design patterns:
185//!
186//! ### Command Trait Pattern
187//!
188//! All Docker commands implement the `DockerCommand` trait, providing a consistent interface:
189//!
190//! - `new()` - Create a new command instance
191//! - `execute()` - Run the command and return typed output
192//! - Builder methods for setting options
193//!
194//! ### Builder Pattern
195//!
196//! Commands use the builder pattern for configuration, allowing fluent and intuitive API usage:
197//!
198//! ```rust,no_run
199//! # use docker_wrapper::{DockerCommand, RunCommand};
200//! # #[tokio::main]
201//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
202//! RunCommand::new("alpine")
203//! .name("my-container")
204//! .env("KEY", "value")
205//! .volume("/host/path", "/container/path")
206//! .workdir("/app")
207//! .cmd(vec!["echo".to_string(), "hello".to_string()])
208//! .execute()
209//! .await?;
210//! # Ok(())
211//! # }
212//! ```
213//!
214//! ### Error Handling
215//!
216//! All operations return `Result<T, docker_wrapper::Error>`, providing detailed error information:
217//!
218//! ```rust,no_run
219//! # use docker_wrapper::{DockerCommand, RunCommand};
220//! # #[tokio::main]
221//! # async fn main() {
222//! match RunCommand::new("invalid:image").execute().await {
223//! Ok(output) => println!("Container ID: {}", output.0),
224//! Err(e) => eprintln!("Failed to run container: {}", e),
225//! }
226//! # }
227//! ```
228//!
229//! ## Command Coverage
230//!
231//! ### Container Commands
232//! - `run` - Run a new container
233//! - `exec` - Execute commands in running containers
234//! - `ps` - List containers
235//! - `create` - Create a new container without starting it
236//! - `start` - Start stopped containers
237//! - `stop` - Stop running containers
238//! - `restart` - Restart containers
239//! - `kill` - Kill running containers
240//! - `rm` - Remove containers
241//! - `pause` - Pause running containers
242//! - `unpause` - Unpause paused containers
243//! - `attach` - Attach to running containers
244//! - `wait` - Wait for containers to stop
245//! - `logs` - Fetch container logs
246//! - `top` - Display running processes in containers
247//! - `stats` - Display resource usage statistics
248//! - `port` - List port mappings
249//! - `rename` - Rename containers
250//! - `update` - Update container configuration
251//! - `cp` - Copy files between containers and host
252//! - `diff` - Inspect filesystem changes
253//! - `export` - Export container filesystem
254//! - `commit` - Create image from container
255//!
256//! ### Image Commands
257//! - `images` - List images
258//! - `pull` - Pull images from registry
259//! - `push` - Push images to registry
260//! - `build` - Build images from Dockerfile
261//! - `load` - Load images from tar archive
262//! - `save` - Save images to tar archive
263//! - `rmi` - Remove images
264//! - `tag` - Tag images
265//! - `import` - Import images from tarball
266//! - `history` - Show image history
267//! - `inspect` - Display detailed information
268//! - `search` - Search Docker Hub for images
269//!
270//! ### System Commands
271//! - `info` - Display system information
272//! - `version` - Show Docker version
273//! - `events` - Monitor Docker events
274//! - `login` - Log in to registry
275//! - `logout` - Log out from registry
276//!
277//! ## Prerequisites Check
278//!
279//! Ensure Docker is installed and accessible:
280//!
281//! ```rust,no_run
282//! use docker_wrapper::ensure_docker;
283//!
284//! # #[tokio::main]
285//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
286//! // Check Docker availability and version
287//! let docker_info = ensure_docker().await?;
288//! println!("Docker version: {}.{}.{}",
289//! docker_info.version.major,
290//! docker_info.version.minor,
291//! docker_info.version.patch);
292//! # Ok(())
293//! # }
294//! ```
295//!
296//! ## Best Practices
297//!
298//! ### Resource Cleanup
299//!
300//! Always clean up containers and resources:
301//!
302//! ```rust,no_run
303//! # use docker_wrapper::{DockerCommand, RunCommand, RmCommand};
304//! # #[tokio::main]
305//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
306//! // Use auto-remove for temporary containers
307//! RunCommand::new("alpine")
308//! .remove() // Automatically remove when stopped
309//! .execute()
310//! .await?;
311//!
312//! // Or manually remove containers
313//! RmCommand::new("my-container")
314//! .force()
315//! .execute()
316//! .await?;
317//! # Ok(())
318//! # }
319//! ```
320//!
321//! ### Error Handling
322//!
323//! Handle errors appropriately for production use:
324//!
325//! ```rust,no_run
326//! # use docker_wrapper::{DockerCommand, RunCommand, Error};
327//! # #[tokio::main]
328//! # async fn main() {
329//! async fn run_container() -> Result<String, Error> {
330//! let output = RunCommand::new("nginx")
331//! .detach()
332//! .execute()
333//! .await?;
334//! Ok(output.0)
335//! }
336//!
337//! match run_container().await {
338//! Ok(id) => println!("Started container: {}", id),
339//! Err(Error::CommandFailed { stderr, .. }) => {
340//! eprintln!("Docker command failed: {}", stderr);
341//! }
342//! Err(e) => eprintln!("Unexpected error: {}", e),
343//! }
344//! # }
345//! ```
346//!
347//! ## Examples
348//!
349//! The `examples/` directory contains comprehensive examples:
350//!
351//! - `basic_usage.rs` - Common Docker operations
352//! - `container_lifecycle.rs` - Container management patterns
353//! - `docker_compose.rs` - Docker Compose usage
354//! - `streaming.rs` - Real-time output streaming
355//! - `error_handling.rs` - Error handling patterns
356//!
357//! Run examples with:
358//!
359//! ```bash
360//! cargo run --example basic_usage
361//! cargo run --example streaming
362//! cargo run --features compose --example docker_compose
363//! ```
364//!
365//! ## Migration from Docker CLI
366//!
367//! Migrating from shell scripts to `docker-wrapper` is straightforward:
368//!
369//! **Shell:**
370//! ```bash
371//! docker run -d --name web -p 8080:80 nginx:latest
372//! ```
373//!
374//! **Rust:**
375//! ```rust,no_run
376//! # use docker_wrapper::{DockerCommand, RunCommand};
377//! # #[tokio::main]
378//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
379//! RunCommand::new("nginx:latest")
380//! .detach()
381//! .name("web")
382//! .port(8080, 80)
383//! .execute()
384//! .await?;
385//! # Ok(())
386//! # }
387//! ```
388//!
389//! ## Contributing
390//!
391//! Contributions are welcome! Please see the [GitHub repository](https://github.com/joshrotenberg/docker-wrapper)
392//! for contribution guidelines and development setup.
393//!
394//! ## License
395//!
396//! This project is licensed under the MIT License - see the LICENSE file for details.
397
398#![warn(missing_docs)]
399#![warn(clippy::all)]
400#![warn(clippy::pedantic)]
401
402pub mod command;
403#[cfg(feature = "compose")]
404pub mod compose;
405pub mod debug;
406pub mod error;
407pub mod platform;
408pub mod prerequisites;
409pub mod stream;
410#[cfg(any(
411 feature = "templates",
412 feature = "template-redis",
413 feature = "template-redis-cluster",
414 feature = "template-postgres",
415 feature = "template-mysql",
416 feature = "template-mongodb",
417 feature = "template-nginx"
418))]
419/// Container templates module
420///
421/// Provides pre-configured container templates with sensible defaults for common services.
422/// Templates support custom images, platforms, persistence, and resource configuration.
423///
424/// See the [Template Guide](https://github.com/joshrotenberg/docker-wrapper/blob/main/docs/TEMPLATES.md) for comprehensive documentation.
425///
426/// # Available Templates
427///
428/// ## Redis Templates
429/// - [`RedisTemplate`] - Basic Redis server
430/// - [`RedisSentinelTemplate`] - High-availability Redis with Sentinel
431/// - [`RedisClusterTemplate`] - Sharded Redis cluster
432/// - [`RedisEnterpriseTemplate`] - Redis Enterprise with management
433/// - [`RedisInsightTemplate`] - Redis management UI
434///
435/// ## Database Templates
436/// - [`PostgresTemplate`] - PostgreSQL database
437/// - [`MysqlTemplate`] - MySQL database
438/// - [`MongodbTemplate`] - MongoDB document database
439///
440/// ## Web Server Templates
441/// - [`NginxTemplate`] - Nginx web server
442///
443/// # Quick Start
444///
445/// ```rust,no_run
446/// use docker_wrapper::{RedisTemplate, Template};
447///
448/// # #[tokio::main]
449/// # async fn main() -> Result<(), Box<dyn std::error::Error>> {
450/// let redis = RedisTemplate::new("my-redis")
451/// .port(6379)
452/// .password("secret")
453/// .with_persistence("redis-data");
454///
455/// let container_id = redis.start().await?;
456/// println!("Redis started: {}", container_id);
457/// # Ok(())
458/// # }
459/// ```
460#[cfg(any(
461 feature = "templates",
462 feature = "template-redis",
463 feature = "template-redis-cluster",
464 feature = "template-postgres",
465 feature = "template-mysql",
466 feature = "template-mongodb",
467 feature = "template-nginx"
468))]
469pub mod template;
470
471pub use stream::{OutputLine, StreamHandler, StreamResult, StreamableCommand};
472
473pub use command::{
474 attach::{AttachCommand, AttachResult},
475 bake::BakeCommand,
476 build::{BuildCommand, BuildOutput},
477 builder::{BuilderBuildCommand, BuilderPruneCommand, BuilderPruneResult},
478 commit::{CommitCommand, CommitResult},
479 container_prune::{ContainerPruneCommand, ContainerPruneResult},
480 context::{
481 ContextCreateCommand, ContextInfo, ContextInspectCommand, ContextLsCommand,
482 ContextRmCommand, ContextUpdateCommand, ContextUseCommand,
483 },
484 cp::{CpCommand, CpResult},
485 create::{CreateCommand, CreateResult},
486 diff::{DiffCommand, DiffResult, FilesystemChange, FilesystemChangeType},
487 events::{DockerEvent, EventActor, EventsCommand, EventsResult},
488 exec::{ExecCommand, ExecOutput},
489 export::{ExportCommand, ExportResult},
490 history::{HistoryCommand, HistoryResult, ImageLayer},
491 image_prune::{DeletedImage, ImagePruneCommand, ImagePruneResult},
492 images::{ImageInfo, ImagesCommand, ImagesOutput},
493 import::{ImportCommand, ImportResult},
494 info::{DockerInfo as SystemDockerInfo, InfoCommand, InfoOutput, SystemInfo},
495 init::{InitCommand, InitOutput, InitTemplate},
496 inspect::{InspectCommand, InspectOutput},
497 kill::{KillCommand, KillResult},
498 load::{LoadCommand, LoadResult},
499 login::{LoginCommand, LoginOutput},
500 logout::{LogoutCommand, LogoutOutput},
501 logs::LogsCommand,
502 network::{
503 NetworkConnectCommand, NetworkConnectResult, NetworkCreateCommand, NetworkCreateResult,
504 NetworkDisconnectCommand, NetworkDisconnectResult, NetworkInfo, NetworkInspectCommand,
505 NetworkInspectOutput, NetworkLsCommand, NetworkLsOutput, NetworkPruneCommand,
506 NetworkPruneResult, NetworkRmCommand, NetworkRmResult,
507 },
508 pause::{PauseCommand, PauseResult},
509 port::{PortCommand, PortMapping as PortMappingInfo, PortResult},
510 ps::{ContainerInfo, PsCommand, PsFormat, PsOutput},
511 pull::PullCommand,
512 push::PushCommand,
513 rename::{RenameCommand, RenameResult},
514 restart::{RestartCommand, RestartResult},
515 rm::{RmCommand, RmResult},
516 rmi::{RmiCommand, RmiResult},
517 run::{ContainerId, MountType, RunCommand, VolumeMount},
518 save::{SaveCommand, SaveResult},
519 search::{RepositoryInfo, SearchCommand, SearchOutput},
520 start::{StartCommand, StartResult},
521 stats::{ContainerStats, StatsCommand, StatsResult},
522 stop::{StopCommand, StopResult},
523 system::{
524 BuildCacheInfo, BuildCacheUsage, ContainerInfo as SystemContainerInfo, ContainerUsage,
525 DiskUsage, ImageInfo as SystemImageInfo, ImageUsage, PruneResult, SystemDfCommand,
526 SystemPruneCommand, VolumeInfo as SystemVolumeInfo, VolumeUsage,
527 },
528 tag::{TagCommand, TagResult},
529 top::{ContainerProcess, TopCommand, TopResult},
530 unpause::{UnpauseCommand, UnpauseResult},
531 update::{UpdateCommand, UpdateResult},
532 version::{ClientVersion, ServerVersion, VersionCommand, VersionInfo, VersionOutput},
533 volume::{
534 VolumeCreateCommand, VolumeCreateResult, VolumeInfo, VolumeInspectCommand,
535 VolumeInspectOutput, VolumeLsCommand, VolumeLsOutput, VolumePruneCommand,
536 VolumePruneResult, VolumeRmCommand, VolumeRmResult,
537 },
538 wait::{WaitCommand, WaitResult},
539 CommandExecutor, CommandOutput, DockerCommand, EnvironmentBuilder, PortBuilder, PortMapping,
540 Protocol,
541};
542pub use debug::{BackoffStrategy, DebugConfig, DebugExecutor, DryRunPreview, RetryPolicy};
543pub use error::{Error, Result};
544pub use platform::{Platform, PlatformInfo, Runtime};
545pub use prerequisites::{ensure_docker, DockerInfo, DockerPrerequisites};
546
547#[cfg(any(
548 feature = "templates",
549 feature = "template-redis",
550 feature = "template-redis-cluster",
551 feature = "template-postgres",
552 feature = "template-mysql",
553 feature = "template-mongodb",
554 feature = "template-nginx"
555))]
556pub use template::{Template, TemplateBuilder, TemplateConfig, TemplateError};
557
558// Redis templates
559#[cfg(feature = "template-redis")]
560pub use template::redis::{RedisInsightTemplate, RedisTemplate};
561
562#[cfg(feature = "template-redis")]
563pub use template::redis::{RedisSentinelTemplate, SentinelConnectionInfo, SentinelInfo};
564
565#[cfg(feature = "template-redis-cluster")]
566pub use template::redis::{
567 ClusterInfo, NodeInfo, NodeRole, RedisClusterConnection, RedisClusterTemplate,
568};
569
570#[cfg(feature = "template-redis-enterprise")]
571pub use template::redis::{RedisEnterpriseConnectionInfo, RedisEnterpriseTemplate};
572
573// Database templates
574#[cfg(feature = "template-postgres")]
575pub use template::database::{PostgresConnectionString, PostgresTemplate};
576
577#[cfg(feature = "template-mysql")]
578pub use template::database::{MysqlConnectionString, MysqlTemplate};
579
580#[cfg(feature = "template-mongodb")]
581pub use template::database::{MongodbConnectionString, MongodbTemplate};
582
583// Web server templates
584#[cfg(feature = "template-nginx")]
585pub use template::web::NginxTemplate;
586
587/// The version of this crate
588pub const VERSION: &str = env!("CARGO_PKG_VERSION");
589
590#[cfg(test)]
591mod tests {
592 use super::*;
593
594 #[test]
595 fn test_version() {
596 // Verify version follows semver format (major.minor.patch)
597 let parts: Vec<&str> = VERSION.split('.').collect();
598 assert!(parts.len() >= 3, "Version should have at least 3 parts");
599
600 // Verify each part is numeric
601 for part in &parts[0..3] {
602 assert!(
603 part.chars().all(|c| c.is_ascii_digit()),
604 "Version parts should be numeric"
605 );
606 }
607 }
608}