testcontainers_modules/cncf_distribution/
mod.rs

1use testcontainers::{core::WaitFor, Image};
2
3const NAME: &str = "registry";
4const TAG: &str = "2";
5
6/// Module to work with a custom Docker registry inside of tests.
7///
8/// Starts an instance of [`CNCF Distribution`], an easy-to-use registry for container images.
9///
10/// # Example
11/// ```
12/// use testcontainers_modules::{cncf_distribution, testcontainers::runners::SyncRunner};
13///
14/// let registry = cncf_distribution::CncfDistribution::default()
15///     .start()
16///     .unwrap();
17///
18/// let image_name = "test";
19/// let image_tag = format!(
20///     "{}:{}/{image_name}",
21///     registry.get_host().unwrap(),
22///     registry.get_host_port_ipv4(5000).unwrap()
23/// );
24///
25/// // now you can push an image tagged with `image_tag` and pull it afterward
26/// ```
27///
28/// [`CNCF Distribution`]: https://distribution.github.io/distribution/
29#[derive(Debug, Default, Clone)]
30pub struct CncfDistribution {
31    /// (remove if there is another variable)
32    /// Field is included to prevent this struct to be a unit struct.
33    /// This allows extending functionality (and thus further variables) without breaking changes
34    _priv: (),
35}
36
37impl Image for CncfDistribution {
38    fn name(&self) -> &str {
39        NAME
40    }
41
42    fn tag(&self) -> &str {
43        TAG
44    }
45
46    fn ready_conditions(&self) -> Vec<WaitFor> {
47        vec![WaitFor::message_on_stderr("listening on [::]:5000")]
48    }
49}
50
51#[cfg(test)]
52mod tests {
53    use futures::StreamExt;
54    use testcontainers::{
55        bollard::{
56            query_parameters::{
57                CreateImageOptionsBuilder, PushImageOptionsBuilder, RemoveImageOptions,
58            },
59            Docker,
60        },
61        runners::AsyncBuilder,
62        GenericBuildableImage, Image,
63    };
64
65    use crate::{cncf_distribution, testcontainers::runners::AsyncRunner};
66
67    const DOCKERFILE: &str = "
68        FROM scratch
69        COPY hello.sh /
70    ";
71
72    #[tokio::test]
73    async fn distribution_push_pull_image() -> Result<(), Box<dyn std::error::Error + 'static>> {
74        let _ = pretty_env_logger::try_init();
75        let distribution_node = cncf_distribution::CncfDistribution::default()
76            .start()
77            .await?;
78        let docker = Docker::connect_with_local_defaults().unwrap();
79
80        let image_name = &format!(
81            "{}:{}/test",
82            distribution_node.get_host().await?,
83            distribution_node.get_host_port_ipv4(5000).await?
84        );
85        let image_tag = "latest";
86
87        let image = GenericBuildableImage::new(image_name, image_tag)
88            .with_dockerfile_string(DOCKERFILE)
89            .with_data(b"#!/bin/sh\necho 'Hello World'", "./hello.sh")
90            .build_image()
91            .await?;
92
93        // Push image, and then remove it
94        let mut push_image = docker.push_image(
95            image.name(),
96            Some(PushImageOptionsBuilder::new().tag(image.tag()).build()),
97            None,
98        );
99        while let Some(x) = push_image.next().await {
100            println!("Push image: {:?}", x.unwrap());
101        }
102
103        docker
104            .remove_image(image.name(), None::<RemoveImageOptions>, None)
105            .await
106            .unwrap();
107
108        // Pull image
109        let mut create_image = docker.create_image(
110            Some(
111                CreateImageOptionsBuilder::new()
112                    .from_image(image.name())
113                    .tag(image.tag())
114                    .build(),
115            ),
116            None,
117            None,
118        );
119        while let Some(x) = create_image.next().await {
120            println!("Create image: {:?}", x.unwrap());
121        }
122
123        assert_eq!(
124            docker
125                .inspect_image(image.name())
126                .await
127                .unwrap()
128                .repo_tags
129                .unwrap()[0],
130            format!("{}:{}", image.name(), image.tag())
131        );
132
133        // clean-up
134        docker
135            .remove_image(image.name(), None::<RemoveImageOptions>, None)
136            .await?;
137
138        Ok(())
139    }
140}