1use std::collections::HashMap;
13use std::time::Duration;
14use versions::Versioning;
15
16use crate::config::{Config, ContainerConfig};
17use crate::container::Container;
18use crate::error::TestError;
19use crate::image::Image;
20use crate::port::{Port, PortAccess};
21use crate::probe::{MessageProbe, MessageSource};
22use crate::service::Service;
23use crate::services::flex::dynamic::DynamicFlexConfig;
24
25const FLEX_LOCAL_BASE: &str = "/usr/local/share";
26const FLEX_CONFIG_BASE: &str = "mulesoft/flex-gateway/conf.d";
27const FLEX_SCHEMA: &str = "http";
28
29pub const FLEX_IMAGE_NAME: &str = "mulesoft/flex-gateway";
31
32mod api;
33mod dynamic;
34mod gcl;
35mod policy;
36
37#[cfg(feature = "experimental")]
38mod service;
39
40pub use api::{ApiConfig, ApiConfigBuilder};
41pub use policy::{PolicyConfig, PolicyConfigBuilder};
42
43#[cfg(feature = "experimental")]
44pub use service::{UpstreamServiceConfig, UpstreamServiceConfigBuilder};
45
46#[derive(Debug, Clone)]
48pub struct FlexConfig {
49 hostname: String,
50 image_name: String,
51 version: String,
52 config_mounts: Vec<(String, String)>,
53 ports: Vec<Port>,
54 timeout: Duration,
55 dynamic_config: DynamicFlexConfig,
56}
57
58impl FlexConfig {
59 pub fn hostname(&self) -> &str {
61 &self.hostname
62 }
63
64 pub fn version(&self) -> &str {
66 &self.version
67 }
68
69 pub fn image_name(&self) -> &str {
71 &self.image_name
72 }
73
74 pub fn config_mounts(&self) -> &[(String, String)] {
76 &self.config_mounts
77 }
78
79 pub fn ports(&self) -> &[Port] {
81 &self.ports
82 }
83
84 pub fn timeout(&self) -> Duration {
86 self.timeout
87 }
88
89 pub fn new() -> Self {
91 Self {
92 hostname: "local-flex".to_string(),
93 version: std::env::var("PDK_TEST_FLEX_IMAGE_VERSION")
94 .unwrap_or_else(|_| "latest".to_string()),
95 image_name: std::env::var("PDK_TEST_FLEX_IMAGE_NAME")
96 .unwrap_or_else(|_| FLEX_IMAGE_NAME.to_string()),
97 config_mounts: vec![],
98 ports: vec![],
99 timeout: Duration::from_secs(60),
100 dynamic_config: DynamicFlexConfig::new(),
101 }
102 }
103
104 pub fn builder() -> FlexConfigBuilder {
106 FlexConfigBuilder::new()
107 }
108}
109
110impl Default for FlexConfig {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115
116#[derive(Debug)]
118pub struct FlexConfigBuilder {
119 config: FlexConfig,
120}
121
122impl FlexConfigBuilder {
123 fn new() -> Self {
124 Self {
125 config: FlexConfig::new(),
126 }
127 }
128
129 pub fn hostname<T: Into<String>>(self, hostname: T) -> Self {
132 Self {
133 config: FlexConfig {
134 hostname: hostname.into(),
135 ..self.config
136 },
137 }
138 }
139
140 pub fn version<T: Into<String>>(self, version: T) -> Self {
143 Self {
144 config: FlexConfig {
145 version: version.into(),
146 ..self.config
147 },
148 }
149 }
150
151 pub fn image_name<T: Into<String>>(self, image_name: T) -> Self {
154 Self {
155 config: FlexConfig {
156 image_name: image_name.into(),
157 ..self.config
158 },
159 }
160 }
161
162 pub fn timeout(self, timeout: Duration) -> Self {
164 Self {
165 config: FlexConfig {
166 timeout,
167 ..self.config
168 },
169 }
170 }
171
172 pub fn config_mounts<T, S, D>(self, config_mounts: T) -> Self
174 where
175 T: IntoIterator<Item = (S, D)>,
176 S: Into<String>,
177 D: Into<String>,
178 {
179 Self {
180 config: FlexConfig {
181 config_mounts: config_mounts
182 .into_iter()
183 .map(|(s, d)| (s.into(), d.into()))
184 .collect(),
185 ..self.config
186 },
187 }
188 }
189
190 pub fn ports<T>(self, ports: T) -> Self
192 where
193 T: IntoIterator<Item = Port>,
194 {
195 Self {
196 config: FlexConfig {
197 ports: ports.into_iter().collect(),
198 ..self.config
199 },
200 }
201 }
202
203 pub fn with_api(self, api: ApiConfig) -> Self {
204 let mut ports = self.config.ports;
205 ports.push(api.port);
206 Self {
207 config: FlexConfig {
208 ports,
209 dynamic_config: self.config.dynamic_config.with_api(api),
210 ..self.config
211 },
212 }
213 }
214
215 #[cfg(feature = "experimental")]
216 pub fn with_upstream_service(self, service: UpstreamServiceConfig) -> Self {
217 Self {
218 config: FlexConfig {
219 dynamic_config: self.config.dynamic_config.with_upstream_service(service),
220 ..self.config
221 },
222 }
223 }
224
225 pub fn build(self) -> FlexConfig {
227 self.config
228 }
229}
230
231fn readiness(version: &str, timeout: Duration) -> Result<MessageProbe, TestError> {
232 let versioning = Versioning::new(version)
233 .ok_or_else(|| TestError::Startup(format!("Unable to parse Flex version `{version}`.")))?;
234 let times = if version == "latest" || versioning >= Versioning::new("1.7.0").unwrap() {
235 1
236 } else if versioning >= Versioning::new("1.4.0").unwrap() {
237 2
238 } else {
239 1
240 };
241
242 Ok(MessageProbe::builder("cds: added/updated")
243 .times(times)
244 .timeout(timeout)
245 .source(MessageSource::StdOut)
246 .build())
247}
248
249impl Config for FlexConfig {
250 fn hostname(&self) -> &str {
251 &self.hostname
252 }
253
254 fn port(&self) -> Port {
255 self.ports.first().cloned().unwrap_or_default()
256 }
257
258 fn schema(&self) -> &str {
259 FLEX_SCHEMA
260 }
261
262 fn to_container_config(&self) -> Result<ContainerConfig, TestError> {
263 let config_mounts = self.config_mounts.iter();
264 let dynamic = self.dynamic_config.dirs()?;
265
266 let mounts = config_mounts.chain(dynamic.iter()).map(|(host, flex)| {
267 (
268 host.clone(),
269 FLEX_LOCAL_BASE.to_string(),
270 format!("{FLEX_CONFIG_BASE}/{flex}"),
271 )
272 });
273 let ports = self.ports.iter().map(|&m| PortAccess::published(m));
274
275 Ok(ContainerConfig::builder(
276 self.hostname.clone(),
277 Image::from_repository(&self.image_name).with_version(&self.version),
278 )
279 .ports(ports)
280 .mounts(mounts)
281 .readiness(readiness(&self.version, self.timeout)?)
282 .build())
283 }
284}
285
286#[derive(Default, Clone)]
288pub struct Flex {
289 sockets: HashMap<Port, String>,
290}
291
292impl Flex {
293 pub fn external_url(&self, port: Port) -> Option<String> {
296 self.sockets
297 .get(&port)
298 .map(|socket| format!("{FLEX_SCHEMA}://{socket}"))
299 }
300}
301
302impl Service for Flex {
303 type Config = FlexConfig;
304
305 fn new(_config: &Self::Config, container: &Container) -> Self {
306 Self {
307 sockets: container.sockets().clone(),
308 }
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use std::time::Duration;
315
316 use crate::error::TestError;
317
318 use super::readiness;
319
320 #[test]
321 fn readiness_for_1_3_0() -> Result<(), TestError> {
322 let readiness = readiness("1.3.0", Duration::from_secs(1))?;
323 assert_eq!(readiness.times(), 1);
324
325 Ok(())
326 }
327
328 #[test]
329 fn readiness_for_1_6_0() -> Result<(), TestError> {
330 let readiness = readiness("1.6.0", Duration::from_secs(1))?;
331 assert_eq!(readiness.times(), 2);
332
333 Ok(())
334 }
335
336 #[test]
337 fn readiness_for_1_7_0() -> Result<(), TestError> {
338 let readiness = readiness("1.7.0", Duration::from_secs(1))?;
339 assert_eq!(readiness.times(), 1);
340
341 Ok(())
342 }
343
344 #[test]
345 fn readiness_for_latest() -> Result<(), TestError> {
346 let readiness = readiness("latest", Duration::from_secs(1))?;
347 assert_eq!(readiness.times(), 1);
348
349 Ok(())
350 }
351}