1use crate::opts::{
3 ContainerCommitOpts, ContainerCreateOpts, ContainerListOpts, ContainerPruneOpts,
4 ContainerRemoveOpts, ContainerRestartOpts, ContainerStopOpts, ExecStartOpts,
5};
6use crate::{models, stream};
7
8use std::{io, path::Path, str};
9
10use futures_util::{Stream, TryStreamExt};
11use hyper::Body;
12use serde::Deserialize;
13
14use crate::{
15 api::Exec,
16 conn::{tty, Headers, Payload},
17 opts::ExecCreateOpts,
18 Error, Result,
19};
20use containers_api::url::{append_query, construct_ep, encoded_pair};
21
22impl_api_ty!(Container => id);
23
24impl Container {
25 impl_api_ep! {container: Container, resp
26 Inspect -> &format!("/containers/{}/json", container.id), models::ContainerInspect200Response
27 Logs -> &format!("/containers/{}/logs", container.id), ()
28 DeleteWithOpts -> &format!("/containers/{}", container.id), String, delete
29 }
30
31 api_doc! { Container => Top
32 |
33 pub async fn top(&self, psargs: Option<&str>) -> Result<models::ContainerTop200Response> {
36 let mut ep = format!("/containers/{}/top", self.id);
37 if let Some(ref args) = psargs {
38 append_query(&mut ep, encoded_pair("ps_args", args));
39 }
40 self.docker.get_json(&ep).await
41 }}
42
43 api_doc! { Container => Attach
44 |
45 pub async fn attach(&self) -> Result<tty::Multiplexer> {
51 let inspect = self.inspect().await?;
52 let is_tty = inspect.config.and_then(|c| c.tty).unwrap_or_default();
53 stream::attach(
54 self.docker.clone(),
55 format!(
56 "/containers/{}/attach?stream=1&stdout=1&stderr=1&stdin=1",
57 self.id
58 ),
59 Payload::empty(),
60 is_tty,
61 )
62 .await
63 }}
64
65 api_doc! { Container => Changes
66 |
67 pub async fn changes(&self) -> Result<Option<models::ContainerChanges200Response>> {
69 self.docker
70 .get_json(&format!("/containers/{}/changes", self.id))
71 .await
72 }}
73
74 api_doc! { Container => Export
75 |
76 pub fn export(&self) -> impl Stream<Item = Result<Vec<u8>>> + '_ {
78 self.docker
79 .get_stream(format!("/containers/{}/export", self.id))
80 .map_ok(|c| c.to_vec())
81 }}
82
83 api_doc! { Container => Stats
84 |
85 pub fn stats(&self) -> impl Stream<Item = Result<serde_json::Value>> + Unpin + '_ {
87 let codec = asynchronous_codec::LinesCodec {};
88
89 let reader = Box::pin(
90 self.docker
91 .get_stream(format!("/containers/{}/stats", self.id))
92 .map_err(|e| io::Error::new(io::ErrorKind::Other, e)),
93 )
94 .into_async_read();
95
96 Box::pin(
97 asynchronous_codec::FramedRead::new(reader, codec)
98 .map_err(Error::IO)
99 .and_then(|s: String| async move {
100 log::trace!("{}", s);
101 serde_json::from_str(&s).map_err(Error::SerdeJsonError)
102 }),
103 )
104 }}
105
106 api_doc! { Container => Start
107 |
108 pub async fn start(&self) -> Result<()> {
110 self.docker
111 .post_string(
112 &format!("/containers/{}/start", self.id),
113 Payload::empty(),
114 Headers::none(),
115 )
116 .await
117 .map(|_| ())
118 }}
119
120 api_doc! { Container => Stop
121 |
122 pub async fn stop(&self, opts: &ContainerStopOpts) -> Result<()> {
124 let ep = construct_ep(format!("/containers/{}/stop", self.id), opts.serialize());
125 self.docker
126 .post_string(&ep, Payload::empty(), Headers::none())
127 .await
128 .map(|_| ())
129 }}
130
131 api_doc! { Container => Restart
132 |
133 pub async fn restart(&self, opts: &ContainerRestartOpts) -> Result<()> {
135 let ep = construct_ep(format!("/containers/{}/restart", self.id), opts.serialize());
136 self.docker
137 .post_string(&ep, Payload::empty(), Headers::none())
138 .await
139 .map(|_| ())
140 }}
141
142 api_doc! { Container => Kill
143 |
144 pub async fn kill(&self, signal: Option<&str>) -> Result<()> {
146 let mut ep = format!("/containers/{}/kill", self.id);
147 if let Some(sig) = signal {
148 append_query(&mut ep, encoded_pair("signal", sig));
149 }
150 self.docker
151 .post_string(&ep, Payload::empty(), Headers::none())
152 .await
153 .map(|_| ())
154 }}
155
156 api_doc! { Container => Rename
157 |
158 pub async fn rename(&self, name: &str) -> Result<()> {
160 self.docker
161 .post_string(
162 &format!(
163 "/containers/{}/rename?{}",
164 self.id,
165 encoded_pair("name", name)
166 ),
167 Payload::empty(),
168 Headers::none(),
169 )
170 .await
171 .map(|_| ())
172 }}
173
174 api_doc! { Container => Pause
175 |
176 pub async fn pause(&self) -> Result<()> {
178 self.docker
179 .post_string(
180 &format!("/containers/{}/pause", self.id),
181 Payload::empty(),
182 Headers::none(),
183 )
184 .await
185 .map(|_| ())
186 }}
187
188 api_doc! { Container => Unpause
189 |
190 pub async fn unpause(&self) -> Result<()> {
192 self.docker
193 .post_string(
194 &format!("/containers/{}/unpause", self.id),
195 Payload::empty(),
196 Headers::none(),
197 )
198 .await
199 .map(|_| ())
200 }}
201
202 api_doc! { Container => Wait
203 |
204 pub async fn wait(&self) -> Result<models::ContainerWaitResponse> {
206 self.docker
207 .post_json(
208 format!("/containers/{}/wait", self.id),
209 Payload::empty(),
210 Headers::none(),
211 )
212 .await
213 }}
214
215 api_doc! { Exec
216 |
217 pub async fn exec(
219 &self,
220 create_opts: &ExecCreateOpts,
221 start_opts: &ExecStartOpts,
222 ) -> Result<tty::Multiplexer> {
223 Exec::create_and_start(self.docker.clone(), &self.id, create_opts, start_opts).await
224 }}
225
226 api_doc! { Container => Archive
227 |
228 pub fn copy_from(&self, path: impl AsRef<Path>) -> impl Stream<Item = Result<Vec<u8>>> + '_ {
237 self.docker
238 .get_stream(format!(
239 "/containers/{}/archive?{}",
240 self.id,
241 encoded_pair("path", path.as_ref().to_string_lossy())
242 ))
243 .map_ok(|c| c.to_vec())
244 }}
245
246 api_doc! { PutContainer => Archive
247 |
248 pub async fn copy_file_into<P: AsRef<Path>>(&self, path: P, bytes: &[u8]) -> Result<()> {
253 let path = path.as_ref();
254
255 let mut ar = tar::Builder::new(Vec::new());
256 let mut header = tar::Header::new_gnu();
257 header.set_size(bytes.len() as u64);
258 header.set_mode(0o0644);
259 ar.append_data(
260 &mut header,
261 path.to_path_buf()
262 .iter()
263 .skip(1)
264 .collect::<std::path::PathBuf>(),
265 bytes,
266 )?;
267 let data = ar.into_inner()?;
268
269 self.copy_to(Path::new("/"), data.into()).await.map(|_| ())
270 }}
271
272 api_doc! { PutContainer => Archive
273 |
274 pub async fn copy_to(&self, path: &Path, body: Body) -> Result<()> {
278 self.docker
279 .put(
280 &format!(
281 "/containers/{}/archive?{}",
282 self.id,
283 encoded_pair("path", path.to_string_lossy())
284 ),
285 Payload::XTar(body),
286 )
287 .await
288 .map(|_| ())
289 }}
290
291 api_doc! { Container => ArchiveInfo
292 |
293 pub async fn stat_file<P>(&self, path: P) -> Result<String>
295 where
296 P: AsRef<Path>,
297 {
298 static PATH_STAT_HEADER: &str = "X-Docker-Container-Path-Stat";
299 let resp = self
300 .docker
301 .head(&format!(
302 "/containers/{}/archive?{}",
303 self.id,
304 encoded_pair("path", path.as_ref().to_string_lossy())
305 ))
306 .await?;
307 if let Some(header) = resp.headers().get(PATH_STAT_HEADER) {
308 let header = header.to_str().map_err(|e| {
309 Error::InvalidResponse(format!("response header was invalid - {e}"))
310 })?;
311
312 base64::decode(header)
313 .map_err(|e| {
314 Error::InvalidResponse(format!("expected header to be valid base64 - {e}"))
315 })
316 .and_then(|s| {
317 str::from_utf8(s.as_slice())
318 .map(str::to_string)
319 .map_err(|e| {
320 Error::InvalidResponse(format!(
321 "expected header to be valid utf8 - {e}"
322 ))
323 })
324 })
325 } else {
326 Err(Error::InvalidResponse(format!("missing `{PATH_STAT_HEADER}` header")))
327 }
328 }}
329
330 api_doc! { Image => Commit
331 |
332 pub async fn commit(&self, opts: &ContainerCommitOpts, config: Option<&models::ContainerConfig>) -> Result<String> {
334 #[derive(Deserialize)]
335 struct IdStruct {
336 #[serde(rename = "Id")]
337 id: String,
338 }
339
340 let payload = if let Some(config) = config {
341 Payload::Json(serde_json::to_string(config)?)
342 } else {
343 Payload::Json("{}".into()) };
345
346 self.docker
347 .post_json(
348 format!(
349 "/commit?{}",
350 opts.with_container(self.id().as_ref())
351 .serialize()
352 .unwrap_or_default()
353 ),
354 payload,
355 Headers::none(),
356 )
357 .await
358 .map(|id: IdStruct| id.id)
359 }}
360}
361
362impl Containers {
363 impl_api_ep! {__: Container, resp
364 List -> "/containers/json", models::ContainerSummary
365 Prune -> "/containers/prune", models::ContainerPrune200Response
366 }
367
368 api_doc! { Containers => Create
369 |
370 pub async fn create(&self, opts: &ContainerCreateOpts) -> Result<Container> {
372 let ep = if let Some(name) = opts.name() {
373 construct_ep("/containers/create", Some(encoded_pair("name", name)))
374 } else {
375 "/containers/create".to_owned()
376 };
377 self.docker
378 .post_json(&ep, Payload::Json(opts.serialize_vec()?), Headers::none())
379 .await
380 .map(|resp: models::ContainerCreateResponse| {
381 Container::new(self.docker.clone(), resp.id)
382 })
383 }}
384}