crankshaft_docker/lib.rs
1//! A Docker client that uses [`bollard`].
2
3use std::path::PathBuf;
4
5use bollard::models::ImageDeleteResponseItem;
6use bollard::models::ImageSummary;
7use bollard::query_parameters::ListNodesOptions;
8
9pub mod container;
10pub mod images;
11pub mod service;
12
13use bollard::models::Node;
14use bollard::models::SystemInfo;
15use crankshaft_config::backend::docker::EventConfig;
16use crankshaft_events::Event;
17use crankshaft_events::TaskId;
18use thiserror::Error;
19use tokio::sync::broadcast;
20
21pub use crate::container::Container;
22use crate::images::*;
23
24/// A global error within this crate.
25#[derive(Error, Debug)]
26pub enum Error {
27 /// An error from [`bollard`].
28 #[error(transparent)]
29 Docker(#[from] bollard::errors::Error),
30 /// A required value was missing for a builder field.
31 #[error("missing required builder field `{0}`")]
32 MissingBuilderField(&'static str),
33 /// An error from a message.
34 #[error("{0}")]
35 Message(String),
36}
37
38/// A [`Result`](std::result::Result) with an [`Error`](enum@Error);
39pub type Result<T> = std::result::Result<T, Error>;
40
41/// A Docker client.
42#[derive(Clone, Debug)]
43pub struct Docker(bollard::Docker);
44
45impl Docker {
46 /// Creates a new [`Docker`] with the specified [client](bollard::Docker).
47 pub fn new(client: bollard::Docker) -> Self {
48 Self(client)
49 }
50
51 /// Attempts to create a new [`Docker`] with the default socket connection.
52 pub fn with_socket_defaults() -> Result<Self> {
53 let client = bollard::Docker::connect_with_socket_defaults().map_err(Error::Docker)?;
54 Ok(Self::new(client))
55 }
56
57 /// Attempts to create a new [`Docker`] with the default HTTP connection.
58 pub fn with_http_defaults() -> Result<Self> {
59 let client = bollard::Docker::connect_with_http_defaults().map_err(Error::Docker)?;
60 Ok(Self::new(client))
61 }
62
63 /// Attempts to create a new [`Docker`] with the default connection details.
64 pub fn with_defaults() -> Result<Self> {
65 let client = bollard::Docker::connect_with_defaults().map_err(Error::Docker)?;
66 Ok(Self::new(client))
67 }
68
69 /// Gets a reference to the inner [`bollard::Docker`].
70 pub fn inner(&self) -> &bollard::Docker {
71 &self.0
72 }
73
74 //----------------------------------------------------------------------------------
75 // Images
76 //----------------------------------------------------------------------------------
77
78 /// Gets all of the images stored in the Docker daemon.
79 pub async fn list_images(&self) -> Result<Vec<ImageSummary>> {
80 list_images(self).await
81 }
82
83 /// Ensures that an image exists in the Docker daemon.
84 ///
85 /// If the image does not specify a tag, a default tag of `latest` will be
86 /// used.
87 ///
88 /// It does this by:
89 ///
90 /// * Confirming that the image already exists there, or
91 /// * Pulling the image from the remote repository.
92 ///
93 /// Returns `Ok(None)` if the pull was cancelled via the provided token.
94 pub async fn ensure_image(
95 &self,
96 image: impl Into<String>,
97 token: tokio_util::sync::CancellationToken,
98 events_ctx: Option<(broadcast::Sender<Event>, TaskId)>,
99 ) -> Result<Option<()>> {
100 ensure_image(self, image, token, events_ctx).await
101 }
102
103 /// Removes an image from the Docker daemon.
104 pub async fn remove_image<T: AsRef<str>, U: AsRef<str>>(
105 &self,
106 name: T,
107 tag: U,
108 ) -> Result<impl IntoIterator<Item = ImageDeleteResponseItem> + use<T, U>> {
109 remove_image(self, name, tag).await
110 }
111
112 /// Removes all images from the Docker daemon.
113 pub async fn remove_all_images(&self) -> Result<Vec<ImageDeleteResponseItem>> {
114 remove_all_images(self).await
115 }
116
117 //----------------------------------------------------------------------------------
118 // Containers
119 //----------------------------------------------------------------------------------
120
121 /// Creates a container builder.
122 ///
123 /// This is the typical way you will create containers.
124 pub fn container_builder(&self) -> container::Builder {
125 container::Builder::new(self.0.clone())
126 }
127
128 /// Creates a container from a known id.
129 ///
130 /// You should typically use [`Self::container_builder()`] unless you
131 /// receive the container name externally from a user (say, on the command
132 /// line as an argument).
133 pub fn container_from_name(
134 &self,
135 id: impl Into<String>,
136 stdout: Option<PathBuf>,
137 stderr: Option<PathBuf>,
138 ) -> Container {
139 Container::new(self.0.clone(), id.into(), stdout, stderr)
140 }
141
142 //----------------------------------------------------------------------------------
143 // Nodes
144 //----------------------------------------------------------------------------------
145
146 /// Gets the nodes of the swarm.
147 ///
148 /// This method should only be called for a Docker daemon that has been
149 /// joined to a swarm.
150 pub async fn nodes(&self) -> Result<Vec<Node>> {
151 self.0
152 .list_nodes(None::<ListNodesOptions>)
153 .await
154 .map_err(Into::into)
155 }
156
157 //----------------------------------------------------------------------------------
158 // Services
159 //----------------------------------------------------------------------------------
160
161 /// Creates a service builder.
162 ///
163 /// This is the typical way you will create services.
164 pub fn service_builder(&self) -> service::Builder {
165 service::Builder::new(self.0.clone())
166 }
167
168 //----------------------------------------------------------------------------------
169 // System
170 //----------------------------------------------------------------------------------
171
172 /// Gets the system information.
173 pub async fn info(&self) -> Result<SystemInfo> {
174 self.0.info().await.map_err(Into::into)
175 }
176}
177
178/// Represents options for sending events.
179#[derive(Debug, Clone)]
180pub struct EventOptions {
181 /// The sender for sending events.
182 pub sender: broadcast::Sender<Event>,
183 /// The task id for the events.
184 pub task_id: TaskId,
185 /// Whether or not send the task started event.
186 pub send_start: bool,
187 /// User-controlled event configuration.
188 pub user_config: EventConfig,
189}
190
191#[cfg(test)]
192mod tests {}