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}