bollard_next/image.rs
1//! Image API: creating, manipulating and pushing docker images
2#[cfg(feature = "buildkit")]
3use bollard_buildkit_proto::moby::filesync::packet::file_send_server::FileSendServer as FileSendPacketServer;
4use bytes::Bytes;
5use futures_core::Stream;
6#[cfg(feature = "buildkit")]
7use futures_util::future::{Either, FutureExt};
8#[cfg(feature = "buildkit")]
9use futures_util::stream;
10use futures_util::stream::StreamExt;
11use http::header::CONTENT_TYPE;
12use http::request::Builder;
13use http_body_util::Full;
14use hyper::Method;
15use serde::Serialize;
16use serde_repr::*;
17
18use super::Docker;
19use crate::auth::{DockerCredentials, DockerCredentialsHeader};
20use crate::container::Config;
21use crate::docker::{body_stream, BodyType};
22use crate::errors::Error;
23use crate::models::*;
24
25use std::cmp::Eq;
26use std::collections::HashMap;
27use std::hash::Hash;
28
29/// Parameters available for pulling an image, used in the [Create Image
30/// API](Docker::create_image)
31///
32/// ## Examples
33///
34/// ```rust
35/// use bollard_next::image::CreateImageOptions;
36///
37/// use std::default::Default;
38///
39/// CreateImageOptions{
40/// from_image: "hello-world",
41/// ..Default::default()
42/// };
43/// ```
44///
45/// ```rust
46/// # use bollard_next::image::CreateImageOptions;
47/// # use std::default::Default;
48/// CreateImageOptions::<String>{
49/// ..Default::default()
50/// };
51/// ```
52#[derive(Debug, Clone, Default, PartialEq, Serialize)]
53#[serde(rename_all = "camelCase")]
54pub struct CreateImageOptions<'a, T>
55where
56 T: Into<String> + Serialize,
57{
58 /// Name of the image to pull. The name may include a tag or digest. This parameter may only be
59 /// used when pulling an image. The pull is cancelled if the HTTP connection is closed.
60 pub from_image: T,
61 /// Source to import. The value may be a URL from which the image can be retrieved or `-` to
62 /// read the image from the request body. This parameter may only be used when importing an
63 /// image.
64 pub from_src: T,
65 /// Repository name given to an image when it is imported. The repo may include a tag. This
66 /// parameter may only be used when importing an image.
67 pub repo: T,
68 /// Tag or digest. If empty when pulling an image, this causes all tags for the given image to
69 /// be pulled.
70 pub tag: T,
71 /// Platform in the format `os[/arch[/variant]]`
72 pub platform: T,
73 /// A list of Dockerfile instructions to be applied to the image being created. Changes must be
74 /// URL-encoded! This parameter may only be used when importing an image.
75 #[serde(
76 serialize_with = "crate::docker::serialize_join_newlines",
77 skip_serializing_if = "Vec::is_empty" // if an empty changes parameter is sent, Docker returns a 400 "file with no instructions" error
78 )]
79 pub changes: Vec<&'a str>,
80}
81
82/// Parameters to the [List Images
83/// API](Docker::list_images())
84///
85/// ## Examples
86///
87/// ```rust
88/// use bollard_next::image::ListImagesOptions;
89///
90/// use std::collections::HashMap;
91/// use std::default::Default;
92///
93/// let mut filters = HashMap::new();
94/// filters.insert("dangling", vec!["true"]);
95///
96/// ListImagesOptions{
97/// all: true,
98/// filters,
99/// ..Default::default()
100/// };
101/// ```
102///
103/// ```rust
104/// # use bollard_next::image::ListImagesOptions;
105/// # use std::default::Default;
106/// ListImagesOptions::<String>{
107/// ..Default::default()
108/// };
109/// ```
110///
111#[derive(Debug, Clone, Default, PartialEq, Serialize)]
112pub struct ListImagesOptions<T>
113where
114 T: Into<String> + Eq + Hash + Serialize,
115{
116 /// Show all images. Only images from a final layer (no children) are shown by default.
117 pub all: bool,
118 /// A JSON encoded value of the filters to process on the images list. Available filters:
119 /// - `before`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
120 /// - `dangling`=`true`
121 /// - `label`=`key` or `label`=`"key=value"` of an image label
122 /// - `reference`=(`<image-name>[:<tag>]`)
123 /// - `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
124 #[serde(serialize_with = "crate::docker::serialize_as_json")]
125 pub filters: HashMap<T, Vec<T>>,
126 /// Show digest information as a RepoDigests field on each image.
127 pub digests: bool,
128}
129
130/// Parameters to the [Prune Images API](Docker::prune_images())
131///
132/// ## Examples
133///
134/// ```rust
135/// use bollard_next::image::PruneImagesOptions;
136///
137/// use std::collections::HashMap;
138///
139/// let mut filters = HashMap::new();
140/// filters.insert("until", vec!["10m"]);
141///
142/// PruneImagesOptions{
143/// filters,
144/// };
145/// ```
146///
147/// ```rust
148/// # use bollard_next::image::PruneImagesOptions;
149/// # use std::default::Default;
150/// PruneImagesOptions::<String>{
151/// ..Default::default()
152/// };
153/// ```
154///
155#[derive(Debug, Clone, Default, PartialEq, Serialize)]
156pub struct PruneImagesOptions<T>
157where
158 T: Into<String> + Eq + Hash + Serialize,
159{
160 /// Filters to process on the prune list, encoded as JSON. Available filters:
161 /// - `dangling=<boolean>` When set to `true` (or `1`), prune only unused *and* untagged
162 /// images. When set to `false` (or `0`), all unused images are pruned.
163 /// - `until=<string>` Prune images created before this timestamp. The `<timestamp>` can be
164 /// Unix timestamps, date formatted timestamps, or Go duration strings (e.g. `10m`, `1h30m`)
165 /// computed relative to the daemon machine’s time.
166 /// - `label` (`label=<key>`, `label=<key>=<value>`, `label!=<key>`, or
167 /// `label!=<key>=<value>`) Prune images with (or without, in case `label!=...` is used) the
168 /// specified labels.
169 #[serde(serialize_with = "crate::docker::serialize_as_json")]
170 pub filters: HashMap<T, Vec<T>>,
171}
172
173/// Parameters to the [Search Images API](Docker::search_images())
174///
175/// ## Example
176///
177/// ```rust
178/// use bollard_next::image::SearchImagesOptions;
179/// use std::default::Default;
180/// use std::collections::HashMap;
181///
182/// let mut filters = HashMap::new();
183/// filters.insert("until", vec!["10m"]);
184///
185/// SearchImagesOptions {
186/// term: "hello-world",
187/// filters,
188/// ..Default::default()
189/// };
190/// ```
191///
192/// ```rust
193/// # use bollard_next::image::SearchImagesOptions;
194/// # use std::default::Default;
195/// SearchImagesOptions::<String> {
196/// ..Default::default()
197/// };
198/// ```
199#[derive(Debug, Clone, Default, PartialEq, Serialize)]
200pub struct SearchImagesOptions<T>
201where
202 T: Into<String> + Eq + Hash + Serialize,
203{
204 /// Term to search (required)
205 pub term: T,
206 /// Maximum number of results to return
207 pub limit: Option<u64>,
208 /// A JSON encoded value of the filters to process on the images list. Available filters:
209 /// - `is-automated=(true|false)`
210 /// - `is-official=(true|false)`
211 /// - `stars=<number>` Matches images that has at least 'number' stars.
212 #[serde(serialize_with = "crate::docker::serialize_as_json")]
213 pub filters: HashMap<T, Vec<T>>,
214}
215
216/// Parameters to the [Remove Image API](Docker::remove_image())
217///
218/// ## Examples
219///
220/// ```rust
221/// use bollard_next::image::RemoveImageOptions;
222/// use std::default::Default;
223///
224/// RemoveImageOptions {
225/// force: true,
226/// ..Default::default()
227/// };
228/// ```
229#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize)]
230pub struct RemoveImageOptions {
231 /// Remove the image even if it is being used by stopped containers or has other tags.
232 pub force: bool,
233 /// Do not delete untagged parent images.
234 pub noprune: bool,
235}
236
237/// Parameters to the [Tag Image API](Docker::tag_image())
238///
239/// ## Examples
240///
241/// ```rust
242/// use bollard_next::image::TagImageOptions;
243/// use std::default::Default;
244///
245/// let tag_options = TagImageOptions {
246/// tag: "v1.0.1",
247/// ..Default::default()
248/// };
249/// ```
250///
251/// ```rust
252/// # use bollard_next::image::TagImageOptions;
253/// # use std::default::Default;
254/// let tag_options = TagImageOptions::<String> {
255/// ..Default::default()
256/// };
257/// ```
258#[derive(Debug, Clone, Default, PartialEq, Serialize)]
259pub struct TagImageOptions<T>
260where
261 T: Into<String> + Serialize,
262{
263 /// The repository to tag in. For example, `someuser/someimage`.
264 pub repo: T,
265 /// The name of the new tag.
266 pub tag: T,
267}
268
269/// Parameters to the [Push Image API](Docker::push_image())
270///
271/// ## Examples
272///
273/// ```rust
274/// use bollard_next::image::PushImageOptions;
275///
276/// PushImageOptions {
277/// tag: "v1.0.1",
278/// };
279/// ```
280///
281/// ```
282/// # use bollard_next::image::PushImageOptions;
283/// # use std::default::Default;
284/// PushImageOptions::<String> {
285/// ..Default::default()
286/// };
287/// ```
288#[derive(Debug, Clone, Default, PartialEq, Serialize)]
289pub struct PushImageOptions<T>
290where
291 T: Into<String> + Serialize,
292{
293 /// The tag to associate with the image on the registry.
294 pub tag: T,
295}
296
297/// Parameters to the [Commit Container API](Docker::commit_container())
298///
299/// ## Examples
300///
301/// ```rust
302/// use bollard_next::image::CommitContainerOptions;
303///
304/// CommitContainerOptions {
305/// container: "my-running-container",
306/// pause: true,
307/// ..Default::default()
308/// };
309/// ```
310///
311/// ```
312/// # use bollard_next::image::CommitContainerOptions;
313/// # use std::default::Default;
314/// CommitContainerOptions::<String> {
315/// ..Default::default()
316/// };
317/// ```
318#[derive(Debug, Clone, Default, PartialEq, Serialize)]
319pub struct CommitContainerOptions<T>
320where
321 T: Into<String> + Serialize,
322{
323 /// The ID or name of the container to commit.
324 pub container: T,
325 /// Repository name for the created image.
326 pub repo: T,
327 /// Tag name for the create image.
328 pub tag: T,
329 /// Commit message.
330 pub comment: T,
331 /// Author of the image.
332 pub author: T,
333 /// Whether to pause the container before committing.
334 pub pause: bool,
335 /// `Dockerfile` instructions to apply while committing
336 pub changes: Option<T>,
337}
338
339/// Parameters to the [Build Image API](Docker::build_image())
340///
341/// ## Examples
342///
343/// ```rust
344/// use bollard_next::image::BuildImageOptions;
345///
346/// BuildImageOptions {
347/// dockerfile: "Dockerfile",
348/// t: "my-image",
349/// ..Default::default()
350/// };
351/// ```
352///
353/// ```
354/// # use bollard_next::image::BuildImageOptions;
355/// # use std::default::Default;
356/// BuildImageOptions::<String> {
357/// ..Default::default()
358/// };
359/// ```
360#[derive(Debug, Clone, Default, PartialEq, Serialize)]
361pub struct BuildImageOptions<T>
362where
363 T: Into<String> + Eq + Hash + Serialize,
364{
365 /// Path within the build context to the `Dockerfile`. This is ignored if `remote` is specified and
366 /// points to an external `Dockerfile`.
367 pub dockerfile: T,
368 /// A name and optional tag to apply to the image in the `name:tag` format. If you omit the tag
369 /// the default `latest` value is assumed. You can provide several `t` parameters.
370 pub t: T,
371 /// Extra hosts to add to `/etc/hosts`.
372 pub extrahosts: Option<T>,
373 /// A Git repository URI or HTTP/HTTPS context URI. If the URI points to a single text file,
374 /// the file’s contents are placed into a file called `Dockerfile` and the image is built from
375 /// that file. If the URI points to a tarball, the file is downloaded by the daemon and the
376 /// contents therein used as the context for the build. If the URI points to a tarball and the
377 /// `dockerfile` parameter is also specified, there must be a file with the corresponding path
378 /// inside the tarball.
379 pub remote: T,
380 /// Suppress verbose build output.
381 pub q: bool,
382 /// Do not use the cache when building the image.
383 pub nocache: bool,
384 /// JSON array of images used for build cache resolution.
385 #[serde(serialize_with = "crate::docker::serialize_as_json")]
386 pub cachefrom: Vec<T>,
387 /// Attempt to pull the image even if an older image exists locally.
388 pub pull: bool,
389 /// Remove intermediate containers after a successful build.
390 pub rm: bool,
391 /// Always remove intermediate containers, even upon failure.
392 pub forcerm: bool,
393 /// Set memory limit for build.
394 pub memory: Option<u64>,
395 /// Total memory (memory + swap). Set as `-1` to disable swap.
396 pub memswap: Option<i64>,
397 /// CPU shares (relative weight).
398 pub cpushares: Option<u64>,
399 /// CPUs in which to allow execution (e.g., `0-3`, `0,1`).
400 pub cpusetcpus: T,
401 /// The length of a CPU period in microseconds.
402 pub cpuperiod: Option<u64>,
403 /// Microseconds of CPU time that the container can get in a CPU period.
404 pub cpuquota: Option<u64>,
405 /// JSON map of string pairs for build-time variables. Users pass these values at build-time.
406 /// Docker uses the buildargs as the environment context for commands run via the `Dockerfile`
407 /// RUN instruction, or for variable expansion in other `Dockerfile` instructions.
408 #[serde(serialize_with = "crate::docker::serialize_as_json")]
409 pub buildargs: HashMap<T, T>,
410 #[cfg(feature = "buildkit")]
411 /// Session ID
412 pub session: Option<String>,
413 /// Size of `/dev/shm` in bytes. The size must be greater than 0. If omitted the system uses 64MB.
414 pub shmsize: Option<u64>,
415 /// Squash the resulting images layers into a single layer.
416 pub squash: bool,
417 /// Arbitrary key/value labels to set on the image, as a JSON map of string pairs.
418 #[serde(serialize_with = "crate::docker::serialize_as_json")]
419 pub labels: HashMap<T, T>,
420 /// Sets the networking mode for the run commands during build. Supported standard values are:
421 /// `bridge`, `host`, `none`, and `container:<name|id>`. Any other value is taken as a custom network's
422 /// name to which this container should connect to.
423 pub networkmode: T,
424 /// Platform in the format `os[/arch[/variant]]`
425 pub platform: T,
426 /// Target build stage
427 pub target: T,
428 #[cfg(feature = "buildkit")]
429 /// Specify a custom exporter.
430 pub outputs: Option<ImageBuildOutput<T>>,
431 /// Builder version to use
432 pub version: BuilderVersion,
433}
434
435#[cfg(feature = "buildkit")]
436/// The exporter to use (see [Docker Docs](https://docs.docker.com/reference/cli/docker/buildx/build/#output))
437#[derive(Debug, Clone, PartialEq)]
438pub enum ImageBuildOutput<T>
439where
440 T: Into<String>,
441{
442 /// The local export type writes all result files to a directory on the client.
443 /// The new files will be owned by the current user.
444 /// On multi-platform builds, all results will be put in subdirectories by their platform.
445 /// It takes the destination directory as a first argument.
446 Tar(T),
447 /// The tar export type writes all result files as a single tarball on the client.
448 /// On multi-platform builds all results will be put in subdirectories by their platform.
449 /// It takes the destination directory as a first argument.
450 ///
451 /// **Notice**: The implementation of the underlying `fsutil` protocol is not complete.
452 /// Therefore, special files, permissions, etc. are ignored or not handled correctly.
453 Local(T),
454}
455
456#[cfg(feature = "buildkit")]
457impl<T> Serialize for ImageBuildOutput<T>
458where
459 T: Into<String>,
460{
461 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
462 where
463 S: serde::Serializer,
464 {
465 match self {
466 ImageBuildOutput::Tar(_) => serializer.serialize_str(r#"[{"type": "tar"}]"#),
467 ImageBuildOutput::Local(_) => serializer.serialize_str(r#"[{"type": "local"}]"#),
468 }
469 }
470}
471
472#[cfg(feature = "buildkit")]
473impl<T> ImageBuildOutput<T>
474where
475 T: Into<String>,
476{
477 fn into_string(self) -> ImageBuildOutput<String> {
478 match self {
479 ImageBuildOutput::Tar(path) => ImageBuildOutput::Tar(path.into()),
480 ImageBuildOutput::Local(path) => ImageBuildOutput::Local(path.into()),
481 }
482 }
483}
484
485/// Builder Version to use
486#[derive(Default, Clone, Copy, Debug, PartialEq, Serialize_repr)]
487#[repr(u8)]
488pub enum BuilderVersion {
489 /// BuilderV1 is the first generation builder in docker daemon
490 #[default]
491 BuilderV1 = 1,
492 /// BuilderBuildKit is builder based on moby/buildkit project
493 BuilderBuildKit = 2,
494}
495
496enum ImageBuildBuildkitEither {
497 #[allow(dead_code)]
498 Left(Option<HashMap<String, DockerCredentials>>),
499 Right(Option<HashMap<String, DockerCredentials>>),
500}
501
502/// Parameters to the [Import Image API](Docker::import_image())
503///
504/// ## Examples
505///
506/// ```rust
507/// use bollard_next::image::ImportImageOptions;
508/// use std::default::Default;
509///
510/// ImportImageOptions {
511/// quiet: true,
512/// ..Default::default()
513/// };
514/// ```
515#[derive(Debug, Copy, Clone, Default, PartialEq, Serialize)]
516pub struct ImportImageOptions {
517 /// Suppress progress details during load.
518 pub quiet: bool,
519}
520
521impl Docker {
522 /// ---
523 ///
524 /// # List Images
525 ///
526 /// Returns a list of images on the server. Note that it uses a different, smaller
527 /// representation of an image than inspecting a single image
528 ///
529 /// # Arguments
530 ///
531 /// - An optional [List Images Options](ListImagesOptions) struct.
532 ///
533 /// # Returns
534 ///
535 /// - Vector of [API Images](ImageSummary), wrapped in a Future.
536 ///
537 /// # Examples
538 ///
539 /// ```rust,no_run
540 /// # use bollard_next::Docker;
541 /// # let docker = Docker::connect_with_http_defaults().unwrap();
542 /// use bollard_next::image::ListImagesOptions;
543 ///
544 /// use std::collections::HashMap;
545 /// use std::default::Default;
546 ///
547 /// let mut filters = HashMap::new();
548 /// filters.insert("dangling", vec!["true"]);
549 ///
550 /// let options = Some(ListImagesOptions{
551 /// all: true,
552 /// filters,
553 /// ..Default::default()
554 /// });
555 ///
556 /// docker.list_images(options);
557 /// ```
558 pub async fn list_images<T>(
559 &self,
560 options: Option<ListImagesOptions<T>>,
561 ) -> Result<Vec<ImageSummary>, Error>
562 where
563 T: Into<String> + Eq + Hash + Serialize,
564 {
565 let url = "/images/json";
566
567 let req = self.build_request(
568 url,
569 Builder::new().method(Method::GET),
570 options,
571 Ok(BodyType::Left(Full::new(Bytes::new()))),
572 );
573
574 self.process_into_value(req).await
575 }
576
577 /// ---
578 ///
579 /// # Create Image
580 ///
581 /// Create an image by either pulling it from a registry or importing it.
582 ///
583 /// # Arguments
584 ///
585 /// - An optional [Create Image Options](CreateImageOptions) struct.
586 /// - An optional request body consisting of a tar or tar.gz archive with the root file system
587 /// for the image. If this argument is used, the value of the `from_src` option must be "-".
588 ///
589 /// # Returns
590 ///
591 /// - [Create Image Info](CreateImageInfo), wrapped in an asynchronous
592 /// Stream.
593 ///
594 /// # Examples
595 ///
596 /// ```rust
597 /// # use bollard_next::Docker;
598 /// # let docker = Docker::connect_with_http_defaults().unwrap();
599 /// use bollard_next::image::CreateImageOptions;
600 ///
601 /// use std::default::Default;
602 ///
603 /// let options = Some(CreateImageOptions{
604 /// from_image: "hello-world",
605 /// ..Default::default()
606 /// });
607 ///
608 /// docker.create_image(options, None, None);
609 ///
610 /// // do some other work while the image is pulled from the docker hub...
611 /// ```
612 ///
613 /// # Unsupported
614 ///
615 /// - Import from tarball
616 ///
617 pub fn create_image<T>(
618 &self,
619 options: Option<CreateImageOptions<'_, T>>,
620 root_fs: Option<Bytes>,
621 credentials: Option<DockerCredentials>,
622 ) -> impl Stream<Item = Result<CreateImageInfo, Error>>
623 where
624 T: Into<String> + Serialize + std::fmt::Debug + Clone,
625 {
626 let url = "/images/create";
627
628 let req = self.build_request_with_registry_auth(
629 url,
630 Builder::new().method(Method::POST),
631 options,
632 Ok(BodyType::Left(Full::new(match root_fs {
633 Some(body) => body,
634 None => Bytes::new(),
635 }))),
636 DockerCredentialsHeader::Auth(credentials),
637 );
638
639 self.process_into_stream(req).boxed().map(|res| {
640 if let Ok(CreateImageInfo {
641 error: Some(error), ..
642 }) = res
643 {
644 Err(Error::DockerStreamError { error })
645 } else {
646 res
647 }
648 })
649 }
650
651 /// ---
652 ///
653 /// # Inspect Image
654 ///
655 /// Return low-level information about an image.
656 ///
657 /// # Arguments
658 ///
659 /// - Image name as a string slice.
660 ///
661 /// # Returns
662 ///
663 /// - [ImageInspect](ImageInspect), wrapped in a Future.
664 ///
665 /// # Examples
666 ///
667 /// ```rust
668 /// # use bollard_next::Docker;
669 /// # let docker = Docker::connect_with_http_defaults().unwrap();
670 ///
671 /// use std::default::Default;
672 ///
673 /// docker.inspect_image("hello-world");
674 /// ```
675 pub async fn inspect_image(&self, image_name: &str) -> Result<ImageInspect, Error> {
676 let url = format!("/images/{image_name}/json");
677
678 let req = self.build_request(
679 &url,
680 Builder::new().method(Method::GET),
681 None::<String>,
682 Ok(BodyType::Left(Full::new(Bytes::new()))),
683 );
684
685 self.process_into_value(req).await
686 }
687
688 /// ---
689 ///
690 /// # Inspect an Image by contacting the registry
691 ///
692 /// Return image digest and platform information by contacting the registry
693 ///
694 /// # Arguments
695 ///
696 /// - Image name as a string slice.
697 ///
698 /// # Returns
699 ///
700 /// - [DistributionInspect](DistributionInspect), wrapped in a Future
701 ///
702 /// # Examples
703 /// ```rust
704 /// use bollard::Docker;
705 /// let docker = Docker::connect_with_http_defaults().unwrap();
706 /// docker.inspect_registry_image("ubuntu:jammy", None);
707 /// ```
708 pub async fn inspect_registry_image(
709 &self,
710 image_name: &str,
711 credentials: Option<DockerCredentials>,
712 ) -> Result<DistributionInspect, Error> {
713 let url = format!("/distribution/{image_name}/json");
714
715 let req = self.build_request_with_registry_auth(
716 &url,
717 Builder::new().method(Method::GET),
718 None::<String>,
719 Ok(BodyType::Left(Full::new(Bytes::new()))),
720 DockerCredentialsHeader::Auth(credentials),
721 );
722
723 self.process_into_value(req).await
724 }
725
726 /// ---
727 ///
728 /// # Prune Images
729 ///
730 /// Delete unused images.
731 ///
732 /// # Arguments
733 ///
734 /// - An optional [Prune Images Options](PruneImagesOptions) struct.
735 ///
736 /// # Returns
737 ///
738 /// - a [Prune Image Response](ImagePruneResponse), wrapped in a Future.
739 ///
740 /// # Examples
741 ///
742 /// ```rust
743 /// # use bollard_next::Docker;
744 /// # let docker = Docker::connect_with_http_defaults().unwrap();
745 /// use bollard_next::image::PruneImagesOptions;
746 ///
747 /// use std::collections::HashMap;
748 ///
749 /// let mut filters = HashMap::new();
750 /// filters.insert("until", vec!["10m"]);
751 ///
752 /// let options = Some(PruneImagesOptions {
753 /// filters
754 /// });
755 ///
756 /// docker.prune_images(options);
757 /// ```
758 pub async fn prune_images<T>(
759 &self,
760 options: Option<PruneImagesOptions<T>>,
761 ) -> Result<ImagePruneResponse, Error>
762 where
763 T: Into<String> + Eq + Hash + Serialize,
764 {
765 let url = "/images/prune";
766
767 let req = self.build_request(
768 url,
769 Builder::new().method(Method::POST),
770 options,
771 Ok(BodyType::Left(Full::new(Bytes::new()))),
772 );
773
774 self.process_into_value(req).await
775 }
776
777 /// ---
778 ///
779 /// # Image History
780 ///
781 /// Return parent layers of an image.
782 ///
783 /// # Arguments
784 ///
785 /// - Image name as a string slice.
786 ///
787 /// # Returns
788 ///
789 /// - Vector of [History Response Item](HistoryResponseItem), wrapped in a
790 /// Future.
791 ///
792 /// # Examples
793 ///
794 /// ```rust
795 /// # use bollard_next::Docker;
796 /// # let docker = Docker::connect_with_http_defaults().unwrap();
797 ///
798 /// docker.image_history("hello-world");
799 /// ```
800 pub async fn image_history(&self, image_name: &str) -> Result<Vec<HistoryResponseItem>, Error> {
801 let url = format!("/images/{image_name}/history");
802
803 let req = self.build_request(
804 &url,
805 Builder::new().method(Method::GET),
806 None::<String>,
807 Ok(BodyType::Left(Full::new(Bytes::new()))),
808 );
809
810 self.process_into_value(req).await
811 }
812
813 /// ---
814 ///
815 /// # Search Images
816 ///
817 /// Search for an image on Docker Hub.
818 ///
819 /// # Arguments
820 ///
821 /// - [Search Image Options](SearchImagesOptions) struct.
822 ///
823 /// # Returns
824 ///
825 /// - Vector of [Image Search Response Item](ImageSearchResponseItem) results, wrapped in a
826 /// Future.
827 ///
828 /// # Examples
829 ///
830 /// ```rust
831 /// # use bollard_next::Docker;
832 ///
833 /// use bollard_next::image::SearchImagesOptions;
834 /// use std::default::Default;
835 /// use std::collections::HashMap;
836 ///
837 /// let mut filters = HashMap::new();
838 /// filters.insert("until", vec!["10m"]);
839 ///
840 /// # let docker = Docker::connect_with_http_defaults().unwrap();
841 /// let search_options = SearchImagesOptions {
842 /// term: "hello-world",
843 /// filters,
844 /// ..Default::default()
845 /// };
846 ///
847 /// docker.search_images(search_options);
848 /// ```
849 pub async fn search_images<T>(
850 &self,
851 options: SearchImagesOptions<T>,
852 ) -> Result<Vec<ImageSearchResponseItem>, Error>
853 where
854 T: Into<String> + Eq + Hash + Serialize,
855 {
856 let url = "/images/search";
857
858 let req = self.build_request(
859 url,
860 Builder::new().method(Method::GET),
861 Some(options),
862 Ok(BodyType::Left(Full::new(Bytes::new()))),
863 );
864
865 self.process_into_value(req).await
866 }
867
868 /// ---
869 ///
870 /// # Remove Image
871 ///
872 /// Remove an image, along with any untagged parent images that were referenced by that image.
873 ///
874 /// # Arguments
875 ///
876 /// - Image name as a string slice.
877 /// - An optional [Remove Image Options](RemoveImageOptions) struct.
878 ///
879 /// # Returns
880 ///
881 /// - Vector of [Image Delete Response Item](ImageDeleteResponseItem), wrapped in a
882 /// Future.
883 ///
884 /// # Examples
885 ///
886 /// ```rust
887 /// # use bollard_next::Docker;
888 ///
889 /// use bollard_next::image::RemoveImageOptions;
890 /// use std::default::Default;
891 ///
892 /// # let docker = Docker::connect_with_http_defaults().unwrap();
893 /// let remove_options = Some(RemoveImageOptions {
894 /// force: true,
895 /// ..Default::default()
896 /// });
897 ///
898 /// docker.remove_image("hello-world", remove_options, None);
899 /// ```
900 pub async fn remove_image(
901 &self,
902 image_name: &str,
903 options: Option<RemoveImageOptions>,
904 credentials: Option<DockerCredentials>,
905 ) -> Result<Vec<ImageDeleteResponseItem>, Error> {
906 let url = format!("/images/{image_name}");
907
908 let req = self.build_request_with_registry_auth(
909 &url,
910 Builder::new().method(Method::DELETE),
911 options,
912 Ok(BodyType::Left(Full::new(Bytes::new()))),
913 DockerCredentialsHeader::Auth(credentials),
914 );
915 self.process_into_value(req).await
916 }
917
918 /// ---
919 ///
920 /// # Tag Image
921 ///
922 /// Tag an image so that it becomes part of a repository.
923 ///
924 /// # Arguments
925 ///
926 /// - Image name as a string slice.
927 /// - Optional [Tag Image Options](TagImageOptions) struct.
928 ///
929 /// # Returns
930 ///
931 /// - unit type `()`, wrapped in a Future.
932 ///
933 /// # Examples
934 ///
935 /// ```rust
936 /// # use bollard_next::Docker;
937 ///
938 /// use bollard_next::image::TagImageOptions;
939 /// use std::default::Default;
940 ///
941 /// # let docker = Docker::connect_with_http_defaults().unwrap();
942 /// let tag_options = Some(TagImageOptions {
943 /// tag: "v1.0.1",
944 /// ..Default::default()
945 /// });
946 ///
947 /// docker.tag_image("hello-world", tag_options);
948 /// ```
949 pub async fn tag_image<T>(
950 &self,
951 image_name: &str,
952 options: Option<TagImageOptions<T>>,
953 ) -> Result<(), Error>
954 where
955 T: Into<String> + Serialize,
956 {
957 let url = format!("/images/{image_name}/tag");
958
959 let req = self.build_request(
960 &url,
961 Builder::new().method(Method::POST),
962 options,
963 Ok(BodyType::Left(Full::new(Bytes::new()))),
964 );
965
966 self.process_into_unit(req).await
967 }
968
969 /// ---
970 ///
971 /// # Push Image
972 ///
973 /// Push an image to a registry.
974 ///
975 /// # Arguments
976 ///
977 /// - Image name as a string slice.
978 /// - Optional [Push Image Options](PushImageOptions) struct.
979 /// - Optional [Docker Credentials](DockerCredentials) struct.
980 ///
981 /// # Returns
982 ///
983 /// - unit type `()`, wrapped in a Future.
984 ///
985 /// # Examples
986 ///
987 /// ```rust
988 /// # use bollard_next::Docker;
989 ///
990 /// use bollard_next::auth::DockerCredentials;
991 /// use bollard_next::image::PushImageOptions;
992 ///
993 /// use std::default::Default;
994 ///
995 /// # let docker = Docker::connect_with_http_defaults().unwrap();
996 /// let push_options = Some(PushImageOptions {
997 /// tag: "v1.0.1",
998 /// });
999 ///
1000 /// let credentials = Some(DockerCredentials {
1001 /// username: Some("Jack".to_string()),
1002 /// password: Some("myverysecretpassword".to_string()),
1003 /// ..Default::default()
1004 /// });
1005 ///
1006 /// docker.push_image("hello-world", push_options, credentials);
1007 /// ```
1008 pub fn push_image<T>(
1009 &self,
1010 image_name: &str,
1011 options: Option<PushImageOptions<T>>,
1012 credentials: Option<DockerCredentials>,
1013 ) -> impl Stream<Item = Result<PushImageInfo, Error>>
1014 where
1015 T: Into<String> + Serialize,
1016 {
1017 let url = format!("/images/{image_name}/push");
1018
1019 let req = self.build_request_with_registry_auth(
1020 &url,
1021 Builder::new()
1022 .method(Method::POST)
1023 .header(CONTENT_TYPE, "application/json"),
1024 options,
1025 Ok(BodyType::Left(Full::new(Bytes::new()))),
1026 DockerCredentialsHeader::Auth(Some(credentials.unwrap_or_default())),
1027 );
1028
1029 self.process_into_stream(req).boxed().map(|res| {
1030 if let Ok(PushImageInfo {
1031 error: Some(error), ..
1032 }) = res
1033 {
1034 Err(Error::DockerStreamError { error })
1035 } else {
1036 res
1037 }
1038 })
1039 }
1040
1041 /// ---
1042 ///
1043 /// # Commit Container
1044 ///
1045 /// Create a new image from a container.
1046 ///
1047 /// # Arguments
1048 ///
1049 /// - [Commit Container Options](CommitContainerOptions) struct.
1050 /// - Container [Config](Config) struct.
1051 ///
1052 /// # Returns
1053 ///
1054 /// - [Commit](Commit), wrapped in a Future.
1055 ///
1056 /// # Examples
1057 ///
1058 /// ```rust
1059 /// # use bollard_next::Docker;
1060 /// # let docker = Docker::connect_with_http_defaults().unwrap();
1061 /// use bollard_next::image::CommitContainerOptions;
1062 /// use bollard_next::container::Config;
1063 ///
1064 /// use std::default::Default;
1065 ///
1066 /// let options = CommitContainerOptions{
1067 /// container: "my-running-container",
1068 /// pause: true,
1069 /// ..Default::default()
1070 /// };
1071 ///
1072 /// let config = Config::<String> {
1073 /// ..Default::default()
1074 /// };
1075 ///
1076 /// docker.commit_container(options, config);
1077 /// ```
1078 pub async fn commit_container<T>(
1079 &self,
1080 options: CommitContainerOptions<T>,
1081 config: Config,
1082 ) -> Result<Commit, Error>
1083 where
1084 T: Into<String> + Serialize,
1085 {
1086 let url = "/commit";
1087
1088 let req = self.build_request(
1089 url,
1090 Builder::new().method(Method::POST),
1091 Some(options),
1092 Docker::serialize_payload(Some(config)),
1093 );
1094
1095 self.process_into_value(req).await
1096 }
1097
1098 /// ---
1099 ///
1100 /// # Build Image
1101 ///
1102 /// Build an image from a tar archive with a `Dockerfile` in it.
1103 ///
1104 /// The `Dockerfile` specifies how the image is built from the tar archive. It is typically in
1105 /// the archive's root, but can be at a different path or have a different name by specifying
1106 /// the `dockerfile` parameter.
1107 ///
1108 /// By default, the call to build specifies using BuilderV1, the first generation builder in docker daemon.
1109 ///
1110 /// # Arguments
1111 ///
1112 /// - [Build Image Options](BuildImageOptions) struct.
1113 /// - Optional [Docker Credentials](DockerCredentials) struct.
1114 /// - Tar archive compressed with one of the following algorithms: identity (no compression),
1115 /// gzip, bzip2, xz. Optional [Hyper Body](hyper::body::Body).
1116 ///
1117 /// # Returns
1118 ///
1119 /// - [Create Image Info](CreateImageInfo), wrapped in an asynchronous
1120 /// Stream.
1121 ///
1122 /// # Examples
1123 ///
1124 /// ```rust,no_run
1125 /// # use bollard_next::Docker;
1126 /// # let docker = Docker::connect_with_http_defaults().unwrap();
1127 /// use bollard_next::image::BuildImageOptions;
1128 /// use bollard_next::container::Config;
1129 ///
1130 /// use std::default::Default;
1131 /// use std::fs::File;
1132 /// use std::io::Read;
1133 ///
1134 /// let options = BuildImageOptions{
1135 /// dockerfile: "Dockerfile",
1136 /// t: "my-image",
1137 /// rm: true,
1138 /// ..Default::default()
1139 /// };
1140 ///
1141 /// let mut file = File::open("tarball.tar.gz").unwrap();
1142 /// let mut contents = Vec::new();
1143 /// file.read_to_end(&mut contents).unwrap();
1144 ///
1145 /// docker.build_image(options, None, Some(contents.into()));
1146 /// ```
1147 pub fn build_image<T>(
1148 &self,
1149 options: BuildImageOptions<T>,
1150 credentials: Option<HashMap<String, DockerCredentials>>,
1151 tar: Option<Bytes>,
1152 ) -> impl Stream<Item = Result<BuildInfo, Error>> + '_
1153 where
1154 T: Into<String> + Eq + Hash + Serialize + Clone,
1155 {
1156 let url = "/build";
1157
1158 match (
1159 if cfg!(feature = "buildkit") && options.version == BuilderVersion::BuilderBuildKit {
1160 ImageBuildBuildkitEither::Left(credentials)
1161 } else {
1162 ImageBuildBuildkitEither::Right(credentials)
1163 },
1164 &options,
1165 ) {
1166 #[cfg(feature = "buildkit")]
1167 (
1168 ImageBuildBuildkitEither::Left(creds),
1169 BuildImageOptions {
1170 session: Some(ref sess),
1171 ..
1172 },
1173 ) => {
1174 let session_id = String::clone(sess);
1175 let outputs = options.outputs.clone().map(ImageBuildOutput::into_string);
1176
1177 let req = self.build_request(
1178 url,
1179 Builder::new()
1180 .method(Method::POST)
1181 .header(CONTENT_TYPE, "application/x-tar"),
1182 Some(options),
1183 Ok(BodyType::Left(Full::new(tar.unwrap_or_default()))),
1184 );
1185
1186 let session = stream::once(
1187 self.start_session(session_id, creds, outputs)
1188 .map(|_| Either::Right(()))
1189 .fuse(),
1190 );
1191
1192 let stream = self.process_into_stream::<BuildInfo>(req).map(Either::Left);
1193
1194 stream::select(stream, session)
1195 .filter_map(|either| async move {
1196 match either {
1197 Either::Left(data) => Some(data),
1198 _ => None,
1199 }
1200 })
1201 .boxed()
1202 }
1203 #[cfg(feature = "buildkit")]
1204 (ImageBuildBuildkitEither::Left(_), BuildImageOptions { session: None, .. }) => {
1205 stream::once(futures_util::future::err(
1206 Error::MissingSessionBuildkitError {},
1207 ))
1208 .boxed()
1209 }
1210 #[cfg(not(feature = "buildkit"))]
1211 (ImageBuildBuildkitEither::Left(_), _) => unimplemented!(
1212 "a buildkit enabled build without the 'buildkit' feature should not be possible"
1213 ),
1214 (ImageBuildBuildkitEither::Right(creds), _) => {
1215 let req = self.build_request_with_registry_auth(
1216 url,
1217 Builder::new()
1218 .method(Method::POST)
1219 .header(CONTENT_TYPE, "application/x-tar"),
1220 Some(options),
1221 Ok(BodyType::Left(Full::new(tar.unwrap_or_default()))),
1222 DockerCredentialsHeader::Config(creds),
1223 );
1224
1225 self.process_into_stream(req).boxed()
1226 }
1227 }
1228 .map(|res| {
1229 if let Ok(BuildInfo {
1230 error: Some(error), ..
1231 }) = res
1232 {
1233 Err(Error::DockerStreamError { error })
1234 } else {
1235 res
1236 }
1237 })
1238 }
1239
1240 #[cfg(feature = "buildkit")]
1241 async fn start_session(
1242 &self,
1243 id: String,
1244 credentials: Option<HashMap<String, DockerCredentials>>,
1245 outputs: Option<ImageBuildOutput<String>>,
1246 ) -> Result<(), crate::grpc::error::GrpcError> {
1247 let driver = crate::grpc::driver::moby::Moby::new(self);
1248
1249 let mut auth_provider = crate::grpc::AuthProvider::new();
1250 if let Some(creds) = credentials {
1251 for (host, docker_credentials) in creds {
1252 auth_provider.set_docker_credentials(&host, docker_credentials);
1253 }
1254 }
1255
1256 let auth =
1257 bollard_buildkit_proto::moby::filesync::v1::auth_server::AuthServer::new(auth_provider);
1258
1259 let mut services = match outputs {
1260 Some(ImageBuildOutput::Tar(path)) => {
1261 let filesend_impl =
1262 crate::grpc::FileSendImpl::new(std::path::PathBuf::from(path).as_path());
1263 let filesend =
1264 bollard_buildkit_proto::moby::filesync::v1::file_send_server::FileSendServer::new(
1265 filesend_impl,
1266 );
1267 vec![crate::grpc::GrpcServer::FileSend(filesend)]
1268 }
1269 Some(ImageBuildOutput::Local(path)) => {
1270 let filesendpacket_impl =
1271 crate::grpc::FileSendPacketImpl::new(std::path::PathBuf::from(path).as_path());
1272 let filesendpacket = FileSendPacketServer::new(filesendpacket_impl);
1273 vec![crate::grpc::GrpcServer::FileSendPacket(filesendpacket)]
1274 }
1275 None => vec![],
1276 };
1277
1278 services.push(crate::grpc::GrpcServer::Auth(auth));
1279
1280 crate::grpc::driver::Driver::grpc_handle(driver, &id, services).await?;
1281
1282 Ok(())
1283 }
1284
1285 /// ---
1286 ///
1287 /// # Export Image
1288 ///
1289 /// Get a tarball containing all images and metadata for a repository.
1290 ///
1291 /// The root of the resulting tar file will contain the file "manifest.json". If the export is
1292 /// of an image repository, rather than a single image, there will also be a `repositories` file
1293 /// with a JSON description of the exported image repositories.
1294 /// Additionally, each layer of all exported images will have a sub directory in the archive
1295 /// containing the filesystem of the layer.
1296 ///
1297 /// See the [Docker API documentation](https://docs.docker.com/engine/api/v1.40/#operation/ImageGet)
1298 /// for more information.
1299 /// # Arguments
1300 /// - The `image_name` string referring to an individual image and tag (e.g. alpine:latest)
1301 ///
1302 /// # Returns
1303 /// - An uncompressed TAR archive
1304 pub fn export_image(&self, image_name: &str) -> impl Stream<Item = Result<Bytes, Error>> {
1305 let url = format!("/images/{image_name}/get");
1306 let req = self.build_request(
1307 &url,
1308 Builder::new()
1309 .method(Method::GET)
1310 .header(CONTENT_TYPE, "application/json"),
1311 None::<String>,
1312 Ok(BodyType::Left(Full::new(Bytes::new()))),
1313 );
1314 self.process_into_body(req)
1315 }
1316
1317 /// ---
1318 ///
1319 /// # Export Images
1320 ///
1321 /// Get a tarball containing all images and metadata for several image repositories. Shared
1322 /// layers will be deduplicated.
1323 ///
1324 /// See the [Docker API documentation](https://docs.docker.com/engine/api/v1.40/#tag/Image/operation/ImageGetAll)
1325 /// for more information.
1326 /// # Arguments
1327 /// - The `image_names` Vec of image names.
1328 ///
1329 /// # Returns
1330 /// - An uncompressed TAR archive
1331 pub fn export_images(&self, image_names: &[&str]) -> impl Stream<Item = Result<Bytes, Error>> {
1332 let options: Vec<_> = image_names.iter().map(|name| ("names", name)).collect();
1333 let req = self.build_request(
1334 "/images/get",
1335 Builder::new()
1336 .method(Method::GET)
1337 .header(CONTENT_TYPE, "application/json"),
1338 Some(options),
1339 Ok(BodyType::Left(Full::new(Bytes::new()))),
1340 );
1341 self.process_into_body(req)
1342 }
1343
1344 /// ---
1345 ///
1346 /// # Import Image
1347 ///
1348 /// Load a set of images and tags into a repository.
1349 ///
1350 /// For details on the format, see the [export image
1351 /// endpoint](struct.Docker.html#method.export_image).
1352 ///
1353 /// # Arguments
1354 /// - [Image Import Options](ImportImageOptions) struct.
1355 ///
1356 /// # Returns
1357 ///
1358 /// - [Build Info](BuildInfo), wrapped in an asynchronous
1359 /// Stream.
1360 ///
1361 /// # Examples
1362 ///
1363 /// ```rust
1364 /// # use bollard::Docker;
1365 /// # let docker = Docker::connect_with_http_defaults().unwrap();
1366 /// use bollard_next::image::ImportImageOptions;
1367 /// use bollard_next::errors::Error;
1368 ///
1369 /// use std::default::Default;
1370 /// use futures_util::stream::{StreamExt, TryStreamExt};
1371 /// use tokio::fs::File;
1372 /// use tokio::io::AsyncWriteExt;
1373 /// use tokio_util::codec;
1374 ///
1375 /// let options = ImportImageOptions{
1376 /// ..Default::default()
1377 /// };
1378 ///
1379 /// async move {
1380 /// let mut file = File::open("tarball.tar.gz").await.unwrap();
1381 ///
1382 /// let mut byte_stream = codec::FramedRead::new(file, codec::BytesCodec::new()).map(|r| {
1383 /// let bytes = r.unwrap().freeze();
1384 /// Ok::<_, Error>(bytes)
1385 /// });
1386 ///
1387 /// let bytes = byte_stream.next().await.unwrap().unwrap();
1388 ///
1389 /// let mut stream = docker
1390 /// .import_image(
1391 /// ImportImageOptions {
1392 /// ..Default::default()
1393 /// },
1394 /// bytes,
1395 /// None,
1396 /// );
1397 ///
1398 /// while let Some(response) = stream.next().await {
1399 /// // ...
1400 /// }
1401 /// };
1402 /// ```
1403 pub fn import_image(
1404 &self,
1405 options: ImportImageOptions,
1406 root_fs: Bytes,
1407 credentials: Option<HashMap<String, DockerCredentials>>,
1408 ) -> impl Stream<Item = Result<BuildInfo, Error>> {
1409 let req = self.build_request_with_registry_auth(
1410 "/images/load",
1411 Builder::new()
1412 .method(Method::POST)
1413 .header(CONTENT_TYPE, "application/json"),
1414 Some(options),
1415 Ok(BodyType::Left(Full::new(root_fs))),
1416 DockerCredentialsHeader::Config(credentials),
1417 );
1418
1419 self.process_into_stream(req).boxed().map(|res| {
1420 if let Ok(BuildInfo {
1421 error: Some(error), ..
1422 }) = res
1423 {
1424 Err(Error::DockerStreamError { error })
1425 } else {
1426 res
1427 }
1428 })
1429 }
1430
1431 /// ---
1432 ///
1433 /// # Import Image (stream)
1434 ///
1435 /// Load a set of images and tags into a repository, without holding it all in memory at a given point in time
1436 ///
1437 /// For details on the format, see the [export image
1438 /// endpoint](struct.Docker.html#method.export_image).
1439 ///
1440 /// # Arguments
1441 /// - [Image Import Options](ImportImageOptions) struct.
1442 /// - Stream producing `Bytes` of the image
1443 ///
1444 /// # Returns
1445 ///
1446 /// - [Build Info](BuildInfo), wrapped in an asynchronous
1447 /// Stream.
1448 ///
1449 /// # Examples
1450 ///
1451 /// ```rust
1452 /// # use bollard::Docker;
1453 /// # let docker = Docker::connect_with_http_defaults().unwrap();
1454 /// use bollard::image::ImportImageOptions;
1455 /// use bollard::errors::Error;
1456 ///
1457 /// use std::default::Default;
1458 /// use futures_util::stream::{StreamExt, TryStreamExt};
1459 /// use tokio::fs::File;
1460 /// use tokio::io::AsyncWriteExt;
1461 /// use tokio_util::codec;
1462 ///
1463 /// let options = ImportImageOptions{
1464 /// ..Default::default()
1465 /// };
1466 ///
1467 /// async move {
1468 /// let mut file = File::open("tarball.tar.gz").await.unwrap();
1469 ///
1470 /// let mut byte_stream = codec::FramedRead::new(file, codec::BytesCodec::new()).map(|r| {
1471 /// r.unwrap().freeze()
1472 /// });
1473 ///
1474 /// let mut stream = docker
1475 /// .import_image_stream(
1476 /// ImportImageOptions {
1477 /// ..Default::default()
1478 /// },
1479 /// byte_stream,
1480 /// None,
1481 /// );
1482 ///
1483 /// while let Some(response) = stream.next().await {
1484 /// // ...
1485 /// }
1486 /// };
1487 /// ```
1488 pub fn import_image_stream(
1489 &self,
1490 options: ImportImageOptions,
1491 root_fs: impl Stream<Item = Bytes> + Send + 'static,
1492 credentials: Option<HashMap<String, DockerCredentials>>,
1493 ) -> impl Stream<Item = Result<BuildInfo, Error>> {
1494 let req = self.build_request_with_registry_auth(
1495 "/images/load",
1496 Builder::new()
1497 .method(Method::POST)
1498 .header(CONTENT_TYPE, "application/json"),
1499 Some(options),
1500 Ok(body_stream(root_fs)),
1501 DockerCredentialsHeader::Config(credentials),
1502 );
1503
1504 self.process_into_stream(req).boxed().map(|res| {
1505 if let Ok(BuildInfo {
1506 error: Some(error), ..
1507 }) = res
1508 {
1509 Err(Error::DockerStreamError { error })
1510 } else {
1511 res
1512 }
1513 })
1514 }
1515}
1516
1517#[cfg(not(windows))]
1518#[cfg(test)]
1519mod tests {
1520
1521 use std::io::Write;
1522
1523 use futures_util::TryStreamExt;
1524 use yup_hyper_mock::HostToReplyConnector;
1525
1526 use crate::{
1527 image::{BuildImageOptions, PushImageOptions},
1528 Docker, API_DEFAULT_VERSION,
1529 };
1530
1531 use super::CreateImageOptions;
1532
1533 #[tokio::test]
1534 async fn test_create_image_with_error() {
1535 let mut connector = HostToReplyConnector::default();
1536 connector.m.insert(
1537 String::from("http://127.0.0.1"),
1538 "HTTP/1.1 200 OK\r\nServer:mock1\r\nContent-Type:application/json\r\n\r\n{\"status\":\"Pulling from localstack/localstack\",\"id\":\"0.14.2\"}\n{\"errorDetail\":{\"message\":\"Get \\\"[https://registry-1.docker.io/v2/localstack/localstack/manifests/sha256:d7aefdaae6712891f13795f538fd855fe4e5a8722249e9ca965e94b69b83b819](https://registry-1.docker.io/v2/localstack/localstack/manifests/sha256:d7aefdaae6712891f13795f538fd855fe4e5a8722249e9ca965e94b69b83b819/)\\\": EOF\"},\"error\":\"Get \\\"[https://registry-1.docker.io/v2/localstack/localstack/manifests/sha256:d7aefdaae6712891f13795f538fd855fe4e5a8722249e9ca965e94b69b83b819](https://registry-1.docker.io/v2/localstack/localstack/manifests/sha256:d7aefdaae6712891f13795f538fd855fe4e5a8722249e9ca965e94b69b83b819/)\\\": EOF\"}".to_string());
1539
1540 let docker =
1541 Docker::connect_with_mock(connector, "127.0.0.1".to_string(), 5, API_DEFAULT_VERSION)
1542 .unwrap();
1543
1544 let image = String::from("localstack");
1545
1546 let result = &docker
1547 .create_image(
1548 Some(CreateImageOptions {
1549 from_image: &image[..],
1550 ..Default::default()
1551 }),
1552 None,
1553 None,
1554 )
1555 .try_collect::<Vec<_>>()
1556 .await;
1557
1558 assert!(matches!(
1559 result,
1560 Err(crate::errors::Error::DockerStreamError { error: _ })
1561 ));
1562 }
1563
1564 #[tokio::test]
1565 async fn test_push_image_with_error() {
1566 let mut connector = HostToReplyConnector::default();
1567 connector.m.insert(
1568 String::from("http://127.0.0.1"),
1569 "HTTP/1.1 200 OK\r\nServer:mock1\r\nContent-Type:application/json\r\n\r\n{\"status\":\"The push refers to repository [localhost:5000/centos]\"}\n{\"status\":\"Preparing\",\"progressDetail\":{},\"id\":\"74ddd0ec08fa\"}\n{\"errorDetail\":{\"message\":\"EOF\"},\"error\":\"EOF\"}".to_string());
1570
1571 let docker =
1572 Docker::connect_with_mock(connector, "127.0.0.1".to_string(), 5, API_DEFAULT_VERSION)
1573 .unwrap();
1574
1575 let image = String::from("centos");
1576
1577 let result = docker
1578 .push_image(&image[..], None::<PushImageOptions<String>>, None)
1579 .try_collect::<Vec<_>>()
1580 .await;
1581
1582 assert!(matches!(
1583 result,
1584 Err(crate::errors::Error::DockerStreamError { error: _ })
1585 ));
1586 }
1587
1588 #[tokio::test]
1589 async fn test_build_image_with_error() {
1590 let mut connector = HostToReplyConnector::default();
1591 connector.m.insert(
1592 String::from("http://127.0.0.1"),
1593 "HTTP/1.1 200 OK\r\nServer:mock1\r\nContent-Type:application/json\r\n\r\n{\"stream\":\"Step 1/2 : FROM alpine\"}\n{\"stream\":\"\n\"}\n{\"status\":\"Pulling from library/alpine\",\"id\":\"latest\"}\n{\"status\":\"Digest: sha256:bc41182d7ef5ffc53a40b044e725193bc10142a1243f395ee852a8d9730fc2ad\"}\n{\"status\":\"Status: Image is up to date for alpine:latest\"}\n{\"stream\":\" --- 9c6f07244728\\n\"}\n{\"stream\":\"Step 2/2 : RUN cmd.exe /C copy nul bollard.txt\"}\n{\"stream\":\"\\n\"}\n{\"stream\":\" --- Running in d615794caf91\\n\"}\n{\"stream\":\"/bin/sh: cmd.exe: not found\\n\"}\n{\"errorDetail\":{\"code\":127,\"message\":\"The command '/bin/sh -c cmd.exe /C copy nul bollard.txt' returned a non-zero code: 127\"},\"error\":\"The command '/bin/sh -c cmd.exe /C copy nul bollard.txt' returned a non-zero code: 127\"}".to_string());
1594 let docker =
1595 Docker::connect_with_mock(connector, "127.0.0.1".to_string(), 5, API_DEFAULT_VERSION)
1596 .unwrap();
1597
1598 let dockerfile = String::from(
1599 r#"FROM alpine
1600 RUN cmd.exe /C copy nul bollard.txt"#,
1601 );
1602
1603 let mut header = tar::Header::new_gnu();
1604 header.set_path("Dockerfile").unwrap();
1605 header.set_size(dockerfile.len() as u64);
1606 header.set_mode(0o755);
1607 header.set_cksum();
1608 let mut tar = tar::Builder::new(Vec::new());
1609 tar.append(&header, dockerfile.as_bytes()).unwrap();
1610
1611 let uncompressed = tar.into_inner().unwrap();
1612 let mut c = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::default());
1613 c.write_all(&uncompressed).unwrap();
1614 let compressed = c.finish().unwrap();
1615
1616 let result = &docker
1617 .build_image(
1618 BuildImageOptions {
1619 dockerfile: "Dockerfile".to_string(),
1620 t: "integration_test_build_image".to_string(),
1621 pull: true,
1622 rm: true,
1623 ..Default::default()
1624 },
1625 None,
1626 Some(compressed.into()),
1627 )
1628 .try_collect::<Vec<_>>()
1629 .await;
1630
1631 println!("{result:#?}");
1632
1633 assert!(matches!(
1634 result,
1635 Err(crate::errors::Error::DockerStreamError { error: _ })
1636 ));
1637 }
1638}