use std::time::Duration;
use rightsize::{Container, ContainerGuard, Result, Wait};
const HTTP_PORT: u16 = 8080;
const MANAGEMENT_PORT: u16 = 9000;
pub struct KeycloakContainer {
container: Container,
admin_username: String,
admin_password: String,
}
impl KeycloakContainer {
pub fn new() -> Self {
Self::with_image("quay.io/keycloak/keycloak:26.4")
}
pub fn with_image(image: &str) -> Self {
let admin_username = "admin".to_string();
let admin_password = "admin".to_string();
let container = Container::new(image)
.with_exposed_ports(&[HTTP_PORT, MANAGEMENT_PORT])
.with_command(&["start-dev"])
.with_env("KC_BOOTSTRAP_ADMIN_USERNAME", &admin_username)
.with_env("KC_BOOTSTRAP_ADMIN_PASSWORD", &admin_password)
.with_env("KC_HEALTH_ENABLED", "true")
.with_memory_limit(1024)
.waiting_for(
Wait::for_http("/health")
.for_port(MANAGEMENT_PORT)
.with_startup_timeout(Duration::from_secs(180)),
);
Self {
container,
admin_username,
admin_password,
}
}
pub fn with_admin_username(mut self, username: &str) -> Self {
self.admin_username = username.to_string();
self.container = self
.container
.with_env("KC_BOOTSTRAP_ADMIN_USERNAME", username);
self
}
pub fn with_admin_password(mut self, password: &str) -> Self {
self.admin_password = password.to_string();
self.container = self
.container
.with_env("KC_BOOTSTRAP_ADMIN_PASSWORD", password);
self
}
pub async fn start(self) -> Result<KeycloakGuard> {
crate::register_default_backends();
let guard = self.container.start().await?;
Ok(KeycloakGuard {
guard,
admin_username: self.admin_username,
admin_password: self.admin_password,
})
}
}
impl Default for KeycloakContainer {
fn default() -> Self {
Self::new()
}
}
pub struct KeycloakGuard {
guard: ContainerGuard,
admin_username: String,
admin_password: String,
}
impl KeycloakGuard {
pub fn admin_username(&self) -> &str {
&self.admin_username
}
pub fn admin_password(&self) -> &str {
&self.admin_password
}
pub fn auth_server_url(&self) -> String {
format!(
"http://{}:{}",
self.guard.host(),
self.guard.get_mapped_port(HTTP_PORT).unwrap()
)
}
pub fn management_url(&self) -> String {
format!(
"http://{}:{}",
self.guard.host(),
self.guard.get_mapped_port(MANAGEMENT_PORT).unwrap()
)
}
pub async fn stop(self) -> Result<()> {
self.guard.stop().await
}
}
impl std::ops::Deref for KeycloakGuard {
type Target = ContainerGuard;
fn deref(&self) -> &ContainerGuard {
&self.guard
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn defaults_are_the_admin_pair() {
let c = KeycloakContainer::new();
assert_eq!(c.admin_username, "admin");
assert_eq!(c.admin_password, "admin");
}
#[test]
fn builders_override_the_defaults() {
let c = KeycloakContainer::new()
.with_admin_username("root")
.with_admin_password("s3cret");
assert_eq!(c.admin_username, "root");
assert_eq!(c.admin_password, "s3cret");
}
}