Skip to content

Commit 508d6fa

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 508d6fa

File tree

1 file changed

+185
-10
lines changed

1 file changed

+185
-10
lines changed

src/valkey/mod.rs

+185-10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
use std::borrow::Cow;
2+
use std::collections::BTreeMap;
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,77 @@ 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().with_valkey_extra_flags("--maxmemory 2mb").start().unwrap();
81+
/// ```
82+
pub fn with_valkey_extra_flags(self, valkey_extra_flags: &str) -> Self {
83+
let mut env_vars = self.env_vars;
84+
env_vars.insert(
85+
"VALKEY_EXTRA_FLAGS".to_string(),
86+
valkey_extra_flags.to_string(),
87+
);
88+
Self {
89+
env_vars,
90+
tag: self.tag,
91+
copy_to_container: self.copy_to_container,
92+
}
93+
}
94+
95+
/// Add custom valkey configuration.
96+
///
97+
/// # Example
98+
/// ```
99+
/// use testcontainers_modules::{
100+
/// testcontainers::runners::SyncRunner,
101+
/// valkey::{Valkey, VALKEY_PORT},
102+
/// };
103+
///
104+
/// let valkey_instance = Valkey::default().with_valkey_conf("maxmemory 2mb".to_string().into_bytes(),).start().unwrap();
105+
/// ```
106+
pub fn with_valkey_conf(self, valky_conf: impl Into<CopyDataSource>) -> Self {
107+
let mut copy_to_container = self.copy_to_container;
108+
copy_to_container.push(CopyToContainer::new(
109+
valky_conf.into(),
110+
"/usr/local/etc/valkey/valkey.conf",
111+
));
112+
Self {
113+
env_vars: self.env_vars,
114+
tag: self.tag,
115+
copy_to_container,
116+
}
117+
}
49118
}
50119

51120
impl Image for Valkey {
@@ -54,28 +123,78 @@ impl Image for Valkey {
54123
}
55124

56125
fn tag(&self) -> &str {
57-
TAG
126+
self.tag.as_deref().unwrap_or(TAG)
58127
}
59128

60129
fn ready_conditions(&self) -> Vec<WaitFor> {
61130
vec![WaitFor::message_on_stdout("Ready to accept connections")]
62131
}
132+
133+
fn env_vars(
134+
&self,
135+
) -> impl IntoIterator<Item = (impl Into<Cow<'_, str>>, impl Into<Cow<'_, str>>)> {
136+
&self.env_vars
137+
}
138+
139+
fn copy_to_sources(&self) -> impl IntoIterator<Item = &CopyToContainer> {
140+
&self.copy_to_container
141+
}
142+
143+
fn cmd(&self) -> impl IntoIterator<Item = impl Into<Cow<'_, str>>> {
144+
if !self.copy_to_container.is_empty() {
145+
vec!["valkey-server", "/usr/local/etc/valkey/valkey.conf"]
146+
} else {
147+
Vec::new()
148+
}
149+
}
63150
}
64151

65152
#[cfg(test)]
66153
mod tests {
154+
use crate::{
155+
testcontainers::runners::SyncRunner, valkey::Valkey, valkey::TAG, valkey::VALKEY_PORT,
156+
};
67157
use redis::Commands;
68-
69-
use crate::{testcontainers::runners::SyncRunner, valkey::Valkey};
158+
use std::collections::HashMap;
159+
use testcontainers::Image;
70160

71161
#[test]
72162
fn valkey_fetch_an_integer() -> Result<(), Box<dyn std::error::Error + 'static>> {
73163
let _ = pretty_env_logger::try_init();
74164
let node = Valkey::default().start()?;
165+
166+
let tag = node.image().tag.clone();
167+
assert_eq!(None, tag);
168+
let tag_from_method = node.image().tag();
169+
assert_eq!(TAG, tag_from_method);
170+
assert_eq!(0, node.image().copy_to_container.len());
171+
75172
let host_ip = node.get_host()?;
76-
let host_port = node.get_host_port_ipv4(6379)?;
173+
let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
77174
let url = format!("redis://{host_ip}:{host_port}");
175+
let client = redis::Client::open(url.as_ref()).unwrap();
176+
let mut con = client.get_connection().unwrap();
177+
178+
con.set::<_, _, ()>("my_key", 42).unwrap();
179+
let result: i64 = con.get("my_key").unwrap();
180+
assert_eq!(42, result);
181+
Ok(())
182+
}
183+
184+
#[test]
185+
fn valkey_latest() -> Result<(), Box<dyn std::error::Error + 'static>> {
186+
let _ = pretty_env_logger::try_init();
187+
let node = Valkey::latest().start()?;
188+
189+
let tag = node.image().tag.clone();
190+
assert_eq!(Some("latest".to_string()), tag);
191+
let tag_from_method = node.image().tag();
192+
assert_eq!("latest", tag_from_method);
193+
assert_eq!(0, node.image().copy_to_container.len());
78194

195+
let host_ip = node.get_host()?;
196+
let host_port = node.get_host_port_ipv4(VALKEY_PORT)?;
197+
let url = format!("redis://{host_ip}:{host_port}");
79198
let client = redis::Client::open(url.as_ref()).unwrap();
80199
let mut con = client.get_connection().unwrap();
81200

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

0 commit comments

Comments
 (0)