libcnb_test/container_config.rs
1use std::collections::{HashMap, HashSet};
2use std::path::PathBuf;
3
4/// Config used when starting a container.
5///
6/// By default the container will run the CNB default process-type, however this can be
7/// overridden using [`ContainerConfig::entrypoint`] and [`ContainerConfig::command`].
8/// See: [CNB App Developer Guide: Run a multi-process app](https://buildpacks.io/docs/app-developer-guide/run-an-app/#run-a-multi-process-app)
9///
10/// # Example
11/// ```no_run
12/// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
13///
14/// TestRunner::default().build(
15/// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
16/// |context| {
17/// // ...
18/// context.start_container(
19/// ContainerConfig::new()
20/// .env("PORT", "12345")
21/// .expose_port(12345),
22/// |container| {
23/// // ...
24/// },
25/// );
26/// },
27/// );
28/// ```
29#[derive(Clone, Default)]
30pub struct ContainerConfig {
31 pub(crate) entrypoint: Option<String>,
32 pub(crate) command: Option<Vec<String>>,
33 pub(crate) env: HashMap<String, String>,
34 pub(crate) exposed_ports: HashSet<u16>,
35 pub(crate) bind_mounts: HashMap<PathBuf, PathBuf>,
36}
37
38impl ContainerConfig {
39 /// Creates an empty [`ContainerConfig`] instance.
40 ///
41 /// By default the container will run the CNB default process-type, however this can be
42 /// overridden using [`ContainerConfig::entrypoint`] and [`ContainerConfig::command`].
43 /// See: [CNB App Developer Guide: Run a multi-process app](https://buildpacks.io/docs/app-developer-guide/run-an-app/#run-a-multi-process-app)
44 ///
45 /// # Example
46 /// ```no_run
47 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
48 ///
49 /// TestRunner::default().build(
50 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
51 /// |context| {
52 /// // ...
53 /// context.start_container(
54 /// ContainerConfig::new()
55 /// .env("PORT", "12345")
56 /// .expose_port(12345),
57 /// |container| {
58 /// // ...
59 /// },
60 /// );
61 /// },
62 /// );
63 /// ```
64 #[must_use]
65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 /// Override the image's `entrypoint` (which is the CNB default process-type).
70 ///
71 /// See: [CNB App Developer Guide: Run a multi-process app](https://buildpacks.io/docs/app-developer-guide/run-an-app/#run-a-multi-process-app)
72 ///
73 /// # Example
74 /// ```no_run
75 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
76 ///
77 /// TestRunner::default().build(
78 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
79 /// |context| {
80 /// // ...
81 /// context.start_container(ContainerConfig::new().entrypoint("worker"), |container| {
82 /// // ...
83 /// });
84 /// },
85 /// );
86 /// ```
87 pub fn entrypoint(&mut self, entrypoint: impl Into<String>) -> &mut Self {
88 self.entrypoint = Some(entrypoint.into());
89 self
90 }
91
92 /// Set the container's `command` (CNB images have no default command).
93 ///
94 /// See: [CNB App Developer Guide: Run a multi-process app](https://buildpacks.io/docs/app-developer-guide/run-an-app/#run-a-multi-process-app)
95 ///
96 /// # Example
97 /// ```no_run
98 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
99 ///
100 /// TestRunner::default().build(
101 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
102 /// |context| {
103 /// // ...
104 /// context.start_container(
105 /// ContainerConfig::new().command(["--additional-arg1", "--additional-arg2"]),
106 /// |container| {
107 /// // ...
108 /// },
109 /// );
110 /// },
111 /// );
112 /// ```
113 pub fn command<I: IntoIterator<Item = S>, S: Into<String>>(&mut self, command: I) -> &mut Self {
114 self.command = Some(command.into_iter().map(S::into).collect());
115 self
116 }
117
118 /// Exposes a given port of the container to the host machine.
119 ///
120 /// The given port is mapped to a random port on the host machine. Use
121 /// [`crate::ContainerContext::address_for_port`] to obtain the local port for a mapped port.
122 ///
123 /// # Example
124 /// ```no_run
125 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
126 ///
127 /// TestRunner::default().build(
128 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
129 /// |context| {
130 /// // ...
131 /// context.start_container(
132 /// ContainerConfig::new()
133 /// .env("PORT", "12345")
134 /// .expose_port(12345),
135 /// |container| {
136 /// let address_on_host = container.address_for_port(12345);
137 /// // ...
138 /// },
139 /// );
140 /// },
141 /// );
142 /// ```
143 pub fn expose_port(&mut self, port: u16) -> &mut Self {
144 self.exposed_ports.insert(port);
145 self
146 }
147
148 /// Inserts or updates an environment variable mapping for the container.
149 ///
150 /// # Example
151 /// ```no_run
152 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
153 ///
154 /// TestRunner::default().build(
155 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
156 /// |context| {
157 /// // ...
158 /// context.start_container(
159 /// ContainerConfig::new()
160 /// .env("PORT", "5678")
161 /// .env("DEBUG", "true"),
162 /// |container| {
163 /// // ...
164 /// },
165 /// );
166 /// },
167 /// );
168 /// ```
169 pub fn env(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
170 self.env.insert(key.into(), value.into());
171 self
172 }
173
174 /// Mount a host file or directory `source` into the container `target`. Useful for
175 /// integration tests that depend on persistent storage shared between container executions.
176 ///
177 /// See: [Docker Engine: Bind Mounts](https://docs.docker.com/engine/storage/bind-mounts/)
178 ///
179 /// # Example
180 /// ```no_run
181 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
182 ///
183 /// TestRunner::default().build(
184 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
185 /// |context| {
186 /// // ...
187 /// context.start_container(
188 /// ContainerConfig::new().bind_mount("/shared/cache", "/workspace/cache"),
189 /// |container| {
190 /// // ...
191 /// },
192 /// );
193 /// },
194 /// );
195 /// ```
196 pub fn bind_mount(
197 &mut self,
198 source: impl Into<PathBuf>,
199 target: impl Into<PathBuf>,
200 ) -> &mut Self {
201 self.bind_mounts.insert(source.into(), target.into());
202 self
203 }
204
205 /// Adds or updates multiple environment variable mappings for the container.
206 ///
207 /// # Example
208 /// ```no_run
209 /// use libcnb_test::{BuildConfig, ContainerConfig, TestRunner};
210 ///
211 /// TestRunner::default().build(
212 /// BuildConfig::new("heroku/builder:22", "tests/fixtures/app"),
213 /// |context| {
214 /// // ...
215 /// context.start_container(
216 /// ContainerConfig::new().envs([("PORT", "5678"), ("DEBUG", "true")]),
217 /// |container| {
218 /// // ...
219 /// },
220 /// );
221 /// },
222 /// );
223 /// ```
224 pub fn envs<K: Into<String>, V: Into<String>, I: IntoIterator<Item = (K, V)>>(
225 &mut self,
226 envs: I,
227 ) -> &mut Self {
228 envs.into_iter().for_each(|(key, value)| {
229 self.env(key.into(), value.into());
230 });
231
232 self
233 }
234}