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}