use rightsize::{Container, ContainerGuard, Result, Wait};
const INTERNAL_ALIAS: &str = "redpanda";
pub struct RedpandaContainer(Container);
impl RedpandaContainer {
const KAFKA_PORT: u16 = 9092;
const INTERNAL_KAFKA_PORT: u16 = 9093;
const SCHEMA_REGISTRY_PORT: u16 = 8081;
pub fn new() -> Self {
Self::with_image("redpandadata/redpanda:v24.2.4")
}
pub fn with_image(image: &str) -> Self {
let container = Container::new(image)
.with_exposed_ports(&[
Self::KAFKA_PORT,
Self::INTERNAL_KAFKA_PORT,
Self::SCHEMA_REGISTRY_PORT,
])
.waiting_for(Wait::for_log_message(
".*Successfully started Redpanda.*",
1,
))
.with_spec_customizer(|mut spec, mapped| {
let kafka_mapped = mapped(Self::KAFKA_PORT);
spec.command = Some(
[
"redpanda",
"start",
"--mode",
"dev-container",
"--smp",
"1",
"--kafka-addr",
"EXTERNAL://0.0.0.0:9092,INTERNAL://0.0.0.0:9093",
"--advertise-kafka-addr",
]
.into_iter()
.map(String::from)
.chain(std::iter::once(format!(
"EXTERNAL://127.0.0.1:{kafka_mapped},INTERNAL://{INTERNAL_ALIAS}:9093"
)))
.chain(
["--schema-registry-addr", "0.0.0.0:8081"]
.into_iter()
.map(String::from),
)
.collect(),
);
spec
});
Self(container)
}
pub async fn start(self) -> Result<RedpandaGuard> {
Ok(RedpandaGuard(self.0.start().await?))
}
}
impl Default for RedpandaContainer {
fn default() -> Self {
Self::new()
}
}
pub struct RedpandaGuard(ContainerGuard);
impl RedpandaGuard {
pub fn bootstrap_servers(&self) -> String {
format!(
"PLAINTEXT://{}:{}",
self.0.host(),
self.0
.get_mapped_port(RedpandaContainer::KAFKA_PORT)
.unwrap()
)
}
pub fn schema_registry_url(&self) -> String {
format!(
"http://{}:{}",
self.0.host(),
self.0
.get_mapped_port(RedpandaContainer::SCHEMA_REGISTRY_PORT)
.unwrap()
)
}
pub async fn stop(self) -> Result<()> {
self.0.stop().await
}
}
impl std::ops::Deref for RedpandaGuard {
type Target = ContainerGuard;
fn deref(&self) -> &ContainerGuard {
&self.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn with_image_smoke() {
let _ = RedpandaContainer::new();
}
}