Skip to content

Commit 932bf6a

Browse files
committed
feat(valkey): add support for custom config and latest image tag
Add support for passing extra flags to Valkey container. Enable mounting a custom `valkey.conf` file for advanced configurations. Allow using `valkey/valkey:latest` image tag for testing against the latest version. Improves flexibility in integration tests by enabling more Valkey features. Closes testcontainers#314
1 parent 0b83d15 commit 932bf6a

File tree

1 file changed

+193
-9
lines changed

1 file changed

+193
-9
lines changed

src/valkey/mod.rs

+193-9
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use std::{borrow::Cow, collections::BTreeMap};
2+
13
use testcontainers::{
24
core::{ContainerPort, WaitFor},
3-
Image,
5+
CopyDataSource, CopyToContainer, Image,
46
};
57

68
const NAME: &str = "valkey/valkey";
7-
const TAG: &str = "8.0.1-alpine";
9+
const TAG: &str = "8.0.2-alpine";
810

911
/// Default port (6379) on which Valkey is exposed
1012
pub const VALKEY_PORT: ContainerPort = ContainerPort::Tcp(6379);
@@ -42,10 +44,83 @@ pub const VALKEY_PORT: ContainerPort = ContainerPort::Tcp(6379);
4244
/// [`VALKEY_PORT`]: super::VALKEY_PORT
4345
#[derive(Debug, Default, Clone)]
4446
pub struct Valkey {
45-
/// (remove if there is another variable)
46-
/// Field is included to prevent this struct to be a unit struct.
47-
/// This allows extending functionality (and thus further variables) without breaking changes
48-
_priv: (),
47+
env_vars: BTreeMap<String, String>,
48+
tag: Option<String>,
49+
copy_to_container: Vec<CopyToContainer>,
50+
}
51+
52+
impl Valkey {
53+
/// Create a new Valkey instance with the latest image.
54+
///
55+
/// # Example
56+
/// ```
57+
/// use testcontainers_modules::{
58+
/// testcontainers::runners::SyncRunner,
59+
/// valkey::{Valkey, VALKEY_PORT},
60+
/// };
61+
///
62+
/// let valkey_instance = Valkey::latest().start().unwrap();
63+
/// ```
64+
pub fn latest() -> Self {
65+
Self {
66+
tag: Some("latest".to_string()),
67+
..Default::default()
68+
}
69+
}
70+
71+
/// Add extra flags by passing additional start arguments.
72+
///
73+
/// # Example
74+
/// ```
75+
/// use testcontainers_modules::{
76+
/// testcontainers::runners::SyncRunner,
77+
/// valkey::{Valkey, VALKEY_PORT},
78+
/// };
79+
///
80+
/// let valkey_instance = Valkey::default()
81+
/// .with_valkey_extra_flags("--maxmemory 2mb")
82+
/// .start()
83+
/// .unwrap();
84+
/// ```
85+
pub fn with_valkey_extra_flags(self, valkey_extra_flags: &str) -> Self {
86+
let mut env_vars = self.env_vars;
87+
env_vars.insert(
88+
"VALKEY_EXTRA_FLAGS".to_string(),
89+
valkey_extra_flags.to_string(),
90+
);
91+
Self {
92+
env_vars,
93+
tag: self.tag,
94+
copy_to_container: self.copy_to_container,
95+
}
96+
}
97+
98+
/// Add custom valkey configuration.
99+
///
100+
/// # Example
101+
/// ```
102+
/// use testcontainers_modules::{
103+
/// testcontainers::runners::SyncRunner,
104+
/// valkey::{Valkey, VALKEY_PORT},
105+
/// };
106+
///
107+
/// let valkey_instance = Valkey::default()
108+
/// .with_valkey_conf("maxmemory 2mb".to_string().into_bytes())
109+
/// .start()
110+
/// .unwrap();
111+
/// ```
112+
pub fn with_valkey_conf(self, valky_conf: impl Into<CopyDataSource>) -> Self {
113+
let mut copy_to_container = self.copy_to_container;
114+
copy_to_container.push(CopyToContainer::new(
115+
valky_conf.into(),
116+
"/usr/local/etc/valkey/valkey.conf",
117+
));
118+
Self {
119+
env_vars: self.env_vars,
120+
tag: self.tag,
121+
copy_to_container,
122+
}
123+
}
49124
}
50125

51126
impl Image for Valkey {
@@ -54,28 +129,81 @@ impl Image for Valkey {
54129
}
55130

56131
fn tag(&self) -> &str {
57-
TAG
132+
self.tag.as_deref().unwrap_or(TAG)
58133
}
59134

60135
fn ready_conditions(&self) -> Vec<WaitFor> {
61136
vec![WaitFor::message_on_stdout("Ready to accept connections")]
62137
}
138+
139+
fn env_vars(
140+
&self,
141+
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
142+
&self.env_vars
143+
}
144+
145+
fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
146+
&self.copy_to_container
147+
}
148+
149+
fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
150+
if !self.copy_to_container.is_empty() {
151+
vec!["valkey-server", "/usr/local/etc/valkey/valkey.conf"]
152+
} else {
153+
Vec::new()
154+
}
155+
}
63156
}
64157

65158
#[cfg(test)]
66159
mod tests {
160+
use std::collections::HashMap;
161+
67162
use redis::Commands;
163+
use testcontainers::Image;
68164

69-
use crate::{testcontainers::runners::SyncRunner, valkey::Valkey};
165+
use crate::{
166+
testcontainers::runners::SyncRunner,
167+
valkey::{Valkey, TAG, VALKEY_PORT},
168+
};
70169

71170
#[test]
72171
fn valkey_fetch_an_integer() -> Result<(), Box<dyn std::error::Error + 'static>> {
73172
let _ = pretty_env_logger::try_init();
74173
let node = Valkey::default().start()?;
174+
175+
let tag = node.image().tag.clone();
176+
assert_eq!(None, tag);
177+
let tag_from_method = node.image().tag();
178+
assert_eq!(TAG, tag_from_method);
179+
assert_eq!(0, node.image().copy_to_container.len());
180+
75181
let host_ip = node.get_host()?;
76-
let host_port = node.get_host_port_ipv4(6379)?;
182+
let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
77183
let url = format!("redis://{host_ip}:{host_port}");
184+
let client = redis::Client::open(url.as_ref()).unwrap();
185+
let mut con = client.get_connection().unwrap();
78186

187+
con.set::<_, _, ()>("my_key", 42).unwrap();
188+
let result: i64 = con.get("my_key").unwrap();
189+
assert_eq!(42, result);
190+
Ok(())
191+
}
192+
193+
#[test]
194+
fn valkey_latest() -> Result<(), Box<dyn std::error::Error + 'static>> {
195+
let _ = pretty_env_logger::try_init();
196+
let node = Valkey::latest().start()?;
197+
198+
let tag = node.image().tag.clone();
199+
assert_eq!(Some("latest".to_string()), tag);
200+
let tag_from_method = node.image().tag();
201+
assert_eq!("latest", tag_from_method);
202+
assert_eq!(0, node.image().copy_to_container.len());
203+
204+
let host_ip = node.get_host()?;
205+
let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
206+
let url = format!("redis://{host_ip}:{host_port}");
79207
let client = redis::Client::open(url.as_ref()).unwrap();
80208
let mut con = client.get_connection().unwrap();
81209

@@ -84,4 +212,60 @@ mod tests {
84212
assert_eq!(42, result);
85213
Ok(())
86214
}
215+
216+
#[test]
217+
fn valkey_extra_flags() -> Result<(), Box<dyn std::error::Error + 'static>> {
218+
let _ = pretty_env_logger::try_init();
219+
let node = Valkey::default()
220+
.with_valkey_extra_flags("--maxmemory 2mb")
221+
.start()?;
222+
let tag = node.image().tag.clone();
223+
assert_eq!(None, tag);
224+
let tag_from_method = node.image().tag();
225+
assert_eq!(TAG, tag_from_method);
226+
assert_eq!(0, node.image().copy_to_container.len());
227+
228+
let host_ip = node.get_host()?;
229+
let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
230+
let url = format!("redis://{host_ip}:{host_port}");
231+
232+
let client = redis::Client::open(url.as_ref()).unwrap();
233+
let mut con = client.get_connection().unwrap();
234+
let max_memory: HashMap<String, isize> = redis::cmd("CONFIG")
235+
.arg("GET")
236+
.arg("maxmemory")
237+
.query(&mut con)
238+
.unwrap();
239+
let max = *max_memory.get("maxmemory").unwrap();
240+
assert_eq!(2097152, max);
241+
Ok(())
242+
}
243+
244+
#[test]
245+
fn valkey_conf() -> Result<(), Box<dyn std::error::Error + 'static>> {
246+
let _ = pretty_env_logger::try_init();
247+
let node = Valkey::default()
248+
.with_valkey_conf("maxmemory 2mb".to_string().into_bytes())
249+
.start()?;
250+
let tag = node.image().tag.clone();
251+
assert_eq!(None, tag);
252+
let tag_from_method = node.image().tag();
253+
assert_eq!(TAG, tag_from_method);
254+
assert_eq!(1, node.image().copy_to_container.len());
255+
256+
let host_ip = node.get_host()?;
257+
let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
258+
let url = format!("redis://{host_ip}:{host_port}");
259+
260+
let client = redis::Client::open(url.as_ref()).unwrap();
261+
let mut con = client.get_connection().unwrap();
262+
let max_memory: HashMap<String, isize> = redis::cmd("CONFIG")
263+
.arg("GET")
264+
.arg("maxmemory")
265+
.query(&mut con)
266+
.unwrap();
267+
let max = *max_memory.get("maxmemory").unwrap();
268+
assert_eq!(2097152, max);
269+
Ok(())
270+
}
87271
}

0 commit comments

Comments
 (0)