1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
use std::fmt::{self, Display};

use indexmap::IndexMap;
use typed_builder::TypedBuilder;

use crate::{ExposedPort, ImageReference, WaitStrategy};

/// Contains configuration require to create and run a container
///
/// # Example
///
/// ```rust
/// # use std::time::Duration;
/// # use rustainers::{RunnableContainer, HealthCheck, ExposedPort, ImageName};
/// let runnable = RunnableContainer::builder()
///     .with_image(ImageName::new("redis"))
///     .with_wait_strategy(
///         HealthCheck::builder()
///             .with_command("redis-cli --raw incr ping")
///             .with_start_period(Duration::from_millis(96))
///             .with_interval(Duration::from_millis(96))
///             .build(),
///     )
///     .with_port_mappings([ExposedPort::new(6379)])
///     .build();
/// ```
///
/// See [existings images](https://github.com/wefoxplatform/rustainers/tree/main/rustainers/src/images/)
/// for real usages.
#[derive(Debug, TypedBuilder)]
#[builder(field_defaults(setter(prefix = "with_")))]
#[non_exhaustive]
pub struct RunnableContainer {
    /// The container image
    #[builder(setter(into))]
    pub(crate) image: ImageReference,

    /// The container name
    #[builder(default, setter(into))]
    pub(crate) container_name: Option<String>,

    /// The command
    #[builder(default, setter(transform = |args: impl IntoIterator<Item = impl Into<String>>| args.into_iter().map(Into::into).collect()))]
    pub(crate) command: Vec<String>,

    /// The environment variables
    #[builder(default, setter(transform = |args: impl IntoIterator<Item = (impl Into<String>, impl Into<String>)>| args.into_iter().map(|(k,v)| (k.into(), v.into())).collect()))]
    pub(crate) env: IndexMap<String, String>,

    /// The wait strategy
    #[builder(default, setter(into))]
    pub(crate) wait_strategy: WaitStrategy,

    /// The ports mapping
    #[builder(default, setter(transform = |args: impl IntoIterator<Item = ExposedPort>| args.into_iter().collect()))]
    pub(crate) port_mappings: Vec<ExposedPort>,
}

impl RunnableContainer {
    /// Build the descriptor of an image (name + tag)
    #[must_use]
    pub fn descriptor(&self) -> String {
        self.image.to_string()
    }
}

impl Display for RunnableContainer {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.descriptor())
    }
}

/// Provide an [`RunnableContainer`] and configure ports
///
/// A container image should implement this trait.
/// See [`crate::images`] for usage.
// TODO implement this trait for a single docker file with build
// TODO derive macro?
pub trait ToRunnableContainer {
    /// Build the runnable container
    fn to_runnable(&self, builder: RunnableContainerBuilder) -> RunnableContainer;
}