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 async fn stats_no_stream(&self, one_shot: Option<bool>) -> Result<models::ContainerStats200Response> {
87 let mut ep = format!("/containers/{}/stats?stream=false", self.id);
88 match one_shot {
89 None => {}
90 Some(param) => { ep.push_str(format!("&one-shot={}", param).as_str()); }
91 }
92 self.docker.get_json(&ep).await
93 }}
94
95 api_doc! { Container => Stats
96 |
97 pub fn stats(&self) -> impl Stream<Item = Result<serde_json::Value>> + Unpin + '_ {
99 let codec = asynchronous_codec::LinesCodec {};
100
101 let reader = Box::pin(
102 self.docker
103 .get_stream(format!("/containers/{}/stats", self.id))
104 .map_err(|e| io::Error::new(io::ErrorKind::Other, e)),
105 )
106 .into_async_read();
107
108 Box::pin(
109 asynchronous_codec::FramedRead::new(reader, codec)
110 .map_err(Error::IO)
111 .and_then(|s: String| async move {
112 log::trace!("{}", s);
113 serde_json::from_str(&s).map_err(Error::SerdeJsonError)
114 }),
115 )
116 }}
117
118 api_doc! { Container => Start
119 |
120 pub async fn start(&self) -> Result<()> {
122 self.docker
123 .post_string(
124 &format!("/containers/{}/start", self.id),
125 Payload::empty(),
126 Headers::none(),
127 )
128 .await
129 .map(|_| ())
130 }}
131
132 api_doc! { Container => Stop
133 |
134 pub async fn stop(&self, opts: &ContainerStopOpts) -> Result<()> {
136 let ep = construct_ep(format!("/containers/{}/stop", self.id), opts.serialize());
137 self.docker
138 .post_string(&ep, Payload::empty(), Headers::none())
139 .await
140 .map(|_| ())
141 }}
142
143 api_doc! { Container => Restart
144 |
145 pub async fn restart(&self, opts: &ContainerRestartOpts) -> Result<()> {
147 let ep = construct_ep(format!("/containers/{}/restart", self.id), opts.serialize());
148 self.docker
149 .post_string(&ep, Payload::empty(), Headers::none())
150 .await
151 .map(|_| ())
152 }}
153
154 api_doc! { Container => Kill
155 |
156 pub async fn kill(&self, signal: Option<&str>) -> Result<()> {
158 let mut ep = format!("/containers/{}/kill", self.id);
159 if let Some(sig) = signal {
160 append_query(&mut ep, encoded_pair("signal", sig));
161 }
162 self.docker
163 .post_string(&ep, Payload::empty(), Headers::none())
164 .await
165 .map(|_| ())
166 }}
167
168 api_doc! { Container => Rename
169 |
170 pub async fn rename(&self, name: &str) -> Result<()> {
172 self.docker
173 .post_string(
174 &format!(
175 "/containers/{}/rename?{}",
176 self.id,
177 encoded_pair("name", name)
178 ),
179 Payload::empty(),
180 Headers::none(),
181 )
182 .await
183 .map(|_| ())
184 }}
185
186 api_doc! { Container => Pause
187 |
188 pub async fn pause(&self) -> Result<()> {
190 self.docker
191 .post_string(
192 &format!("/containers/{}/pause", self.id),
193 Payload::empty(),
194 Headers::none(),
195 )
196 .await
197 .map(|_| ())
198 }}
199
200 api_doc! { Container => Unpause
201 |
202 pub async fn unpause(&self) -> Result<()> {
204 self.docker
205 .post_string(
206 &format!("/containers/{}/unpause", self.id),
207 Payload::empty(),
208 Headers::none(),
209 )
210 .await
211 .map(|_| ())
212 }}
213
214 api_doc! { Container => Wait
215 |
216 pub async fn wait(&self) -> Result<models::ContainerWaitResponse> {
218 self.docker
219 .post_json(
220 format!("/containers/{}/wait", self.id),
221 Payload::empty(),
222 Headers::none(),
223 )
224 .await
225 }}
226
227 api_doc! { Exec
228 |
229 pub async fn exec(
231 &self,
232 create_opts: &ExecCreateOpts,
233 start_opts: &ExecStartOpts,
234 ) -> Result<tty::Multiplexer> {
235 Exec::create_and_start(self.docker.clone(), &self.id, create_opts, start_opts).await
236 }}
237
238 api_doc! { Container => Archive
239 |
240 pub fn copy_from(&self, path: impl AsRef<Path>) -> impl Stream<Item = Result<Vec<u8>>> + '_ {
249 self.docker
250 .get_stream(format!(
251 "/containers/{}/archive?{}",
252 self.id,
253 encoded_pair("path", path.as_ref().to_string_lossy())
254 ))
255 .map_ok(|c| c.to_vec())
256 }}
257
258 api_doc! { PutContainer => Archive
259 |
260 pub async fn copy_file_into<P: AsRef<Path>>(&self, path: P, bytes: &[u8]) -> Result<()> {
265 let path = path.as_ref();
266
267 let mut ar = tar::Builder::new(Vec::new());
268 let mut header = tar::Header::new_gnu();
269 header.set_size(bytes.len() as u64);
270 header.set_mode(0o0644);
271 ar.append_data(
272 &mut header,
273 path.to_path_buf()
274 .iter()
275 .skip(1)
276 .collect::<std::path::PathBuf>(),
277 bytes,
278 )?;
279 let data = ar.into_inner()?;
280
281 self.copy_to(Path::new("/"), data.into()).await.map(|_| ())
282 }}
283
284 api_doc! { PutContainer => Archive
285 |
286 pub async fn copy_to(&self, path: &Path, body: Body) -> Result<()> {
290 self.docker
291 .put(
292 &format!(
293 "/containers/{}/archive?{}",
294 self.id,
295 encoded_pair("path", path.to_string_lossy())
296 ),
297 Payload::XTar(body),
298 )
299 .await
300 .map(|_| ())
301 }}
302
303 api_doc! { Container => ArchiveInfo
304 |
305 pub async fn stat_file<P>(&self, path: P) -> Result<String>
307 where
308 P: AsRef<Path>,
309 {
310 static PATH_STAT_HEADER: &str = "X-Docker-Container-Path-Stat";
311 let resp = self
312 .docker
313 .head(&format!(
314 "/containers/{}/archive?{}",
315 self.id,
316 encoded_pair("path", path.as_ref().to_string_lossy())
317 ))
318 .await?;
319 if let Some(header) = resp.headers().get(PATH_STAT_HEADER) {
320 let header = header.to_str().map_err(|e| {
321 Error::InvalidResponse(format!("response header was invalid - {e}"))
322 })?;
323
324 base64::decode(header)
325 .map_err(|e| {
326 Error::InvalidResponse(format!("expected header to be valid base64 - {e}"))
327 })
328 .and_then(|s| {
329 str::from_utf8(s.as_slice())
330 .map(str::to_string)
331 .map_err(|e| {
332 Error::InvalidResponse(format!(
333 "expected header to be valid utf8 - {e}"
334 ))
335 })
336 })
337 } else {
338 Err(Error::InvalidResponse(format!("missing `{PATH_STAT_HEADER}` header")))
339 }
340 }}
341
342 api_doc! { Image => Commit
343 |
344 pub async fn commit(&self, opts: &ContainerCommitOpts, config: Option<&models::ContainerConfig>) -> Result<String> {
346 #[derive(Deserialize)]
347 struct IdStruct {
348 #[serde(rename = "Id")]
349 id: String,
350 }
351
352 let payload = if let Some(config) = config {
353 Payload::Json(serde_json::to_string(config)?)
354 } else {
355 Payload::Json("{}".into()) };
357
358 self.docker
359 .post_json(
360 format!(
361 "/commit?{}",
362 opts.with_container(self.id().as_ref())
363 .serialize()
364 .unwrap_or_default()
365 ),
366 payload,
367 Headers::none(),
368 )
369 .await
370 .map(|id: IdStruct| id.id)
371 }}
372}
373
374impl Containers {
375 impl_api_ep! {__: Container, resp
376 List -> "/containers/json", models::ContainerSummary
377 Prune -> "/containers/prune", models::ContainerPrune200Response
378 }
379
380 api_doc! { Containers => Create
381 |
382 pub async fn create(&self, opts: &ContainerCreateOpts) -> Result<Container> {
384 let ep = if let Some(name) = opts.name() {
385 construct_ep("/containers/create", Some(encoded_pair("name", name)))
386 } else {
387 "/containers/create".to_owned()
388 };
389 self.docker
390 .post_json(&ep, Payload::Json(opts.serialize_vec()?), Headers::none())
391 .await
392 .map(|resp: rs_docker_api_stubs::models::ContainerCreateResponse| {
393 Container::new(self.docker.clone(), resp.id)
394 })
395 }}
396}