Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add multiple env variables for configuring Redis #202

Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion server/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ fn build_offline(offline_args: OfflineArgs) -> EdgeResult<CacheContainer> {
}

async fn get_data_source(args: &EdgeArgs) -> Option<Arc<dyn EdgePersistence>> {
if let Some(redis_url) = args.redis_url.clone() {
if let Some(redis_url) = args.redis.clone().and_then(|r| r.to_url()) {
let redis_client = RedisPersister::new(&redis_url).expect("Failed to connect to Redis");
return Some(Arc::new(redis_client));
}
Expand Down
198 changes: 195 additions & 3 deletions server/src/cli.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::fmt::{Display, Formatter};
use std::path::PathBuf;

use clap::{ArgGroup, Args, Parser, Subcommand};
use clap::{ArgGroup, Args, Parser, Subcommand, ValueEnum};

#[derive(Subcommand, Debug, Clone)]
#[allow(clippy::large_enum_variant)]
Expand All @@ -11,6 +12,73 @@ pub enum EdgeMode {
Offline(OfflineArgs),
}

#[derive(ValueEnum, Debug, Clone)]
pub enum RedisSchema {
Redis,
Rediss,
RedisUnix,
Unix,
}

impl Display for RedisSchema {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
RedisSchema::Redis => write!(f, "redis"),
RedisSchema::Rediss => write!(f, "rediss"),
RedisSchema::RedisUnix => write!(f, "redis+unix"),
RedisSchema::Unix => write!(f, "unix"),
}
}
}
#[derive(Args, Debug, Clone)]
pub struct RedisArgs {
#[clap(long, env)]
pub redis_url: Option<String>,
#[clap(long, env)]
pub redis_password: Option<String>,
#[clap(long, env)]
pub redis_username: Option<String>,
#[clap(long, env)]
pub redis_port: Option<u16>,
#[clap(long, env)]
pub redis_host: Option<String>,
#[clap(long, env, default_value_t = false)]
pub redis_secure: bool,
#[clap(long, env, default_value_t = RedisSchema::Redis, value_enum)]
pub redis_scheme: RedisSchema,
}

impl RedisArgs {
pub fn to_url(&self) -> Option<String> {
self.redis_url
.clone()
.map(|url| {
reqwest::Url::parse(&url).unwrap_or_else(|_| panic!("Failed to create url from REDIS_URL: {}, REDIS_USERNAME: {} and REDIS_PASSWORD: {}", self.redis_url.clone().unwrap_or("NO_URL".into()), self.redis_username.clone().unwrap_or("NO_USERNAME_SET".into()), self.redis_password.is_some()))
})
.or_else(|| self.redis_host.clone().map(|host| {
reqwest::Url::parse(format!("{}://{}", self.redis_scheme, &host).as_str()).expect("Failed to parse hostname from REDIS_HOSTNAME or --redis-hostname parameters")
}))
.map(|base| {
let mut base_url = base;
if self.redis_password.is_some() {
base_url.set_password(Some(&self.redis_password.clone().unwrap())).expect("Failed to set password");
}
if self.redis_username.is_some() {
base_url.set_username(&self.redis_username.clone().unwrap()).expect("Failed to set username");
}
base_url.set_port(self.redis_port).expect("Failed to set port");
base_url
}).map(|almost_finished_url| {
let mut base_url = almost_finished_url;
if self.redis_secure {
println!("Yves should be secure");
chriswk marked this conversation as resolved.
Show resolved Hide resolved
let scheme = "rediss";
base_url.set_scheme(scheme).expect("Failed to set redis scheme");
}
base_url
}).map(|f| f.to_string())
}
}
#[derive(Args, Debug, Clone)]
pub struct ClientIdentity {
/// Client certificate chain in PEM encoded X509 format with the leaf certificate first.
Expand Down Expand Up @@ -39,8 +107,8 @@ pub struct EdgeArgs {
pub upstream_url: String,

/// A URL pointing to a running Redis instance. Edge will use this instance to persist feature and token data and read this back after restart. Mutually exclusive with the --backup-folder option
#[clap(short, long, env)]
pub redis_url: Option<String>,
#[clap(flatten)]
pub redis: Option<RedisArgs>,

/// A path to a local folder. Edge will write feature and token data to disk in this folder and read this back after restart. Mutually exclusive with the --redis-url option
#[clap(short, long, env)]
Expand Down Expand Up @@ -249,4 +317,128 @@ mod tests {
EdgeMode::Offline(_) => unreachable!(),
}
}

#[test]
pub fn can_create_redis_url_from_redis_url_argument() {
let args = vec![
"unleash-edge",
"edge",
"-u http://localhost:4242",
"--redis-url",
"redis://localhost/redis",
];
let args = CliArgs::parse_from(args);
match args.mode {
EdgeMode::Edge(args) => {
let redis_url = args.redis.unwrap().to_url();
assert!(redis_url.is_some());
assert_eq!(redis_url.unwrap(), "redis://localhost/redis");
}
_ => unreachable!(),
}
}

#[test]
pub fn can_create_redis_url_from_more_specific_redis_arguments() {
let args = vec![
"unleash-edge",
"edge",
"-u http://localhost:4242",
"--redis-host",
"localhost",
"--redis-username",
"redis",
"--redis-password",
"password",
"--redis-port",
"6389",
"--redis-scheme",
"rediss",
];
let args = CliArgs::parse_from(args);
match args.mode {
EdgeMode::Edge(args) => {
let redis_url = args.redis.unwrap().to_url();
assert!(redis_url.is_some());
assert_eq!(redis_url.unwrap(), "rediss://redis:password@localhost:6389");
}
_ => unreachable!(),
}
}

#[test]
pub fn can_combine_redis_url_with_username_and_password() {
let args = vec![
"unleash-edge",
"edge",
"-u http://localhost:4242",
"--redis-url",
"redis://localhost",
"--redis-username",
"redis",
"--redis-password",
"password",
];
let args = CliArgs::parse_from(args);
match args.mode {
EdgeMode::Edge(args) => {
let redis_url = args.redis.unwrap().to_url();
assert!(redis_url.is_some());
assert_eq!(redis_url.unwrap(), "redis://redis:password@localhost");
}
_ => unreachable!(),
}
}

#[test]
pub fn setting_redis_secure_to_true_overrides_set_scheme() {
let args = vec![
"unleash-edge",
"edge",
"-u http://localhost:4242",
"--redis-url",
"redis://localhost",
"--redis-username",
"redis",
"--redis-password",
"password",
"--redis-secure",
];
let args = CliArgs::parse_from(args);
match args.mode {
EdgeMode::Edge(args) => {
let redis_url = args.redis.unwrap().to_url();
assert!(redis_url.is_some());
assert_eq!(redis_url.unwrap(), "rediss://redis:password@localhost");
}
_ => unreachable!(),
}
}

#[test]
pub fn setting_secure_to_true_overrides_the_scheme_for_detailed_arguments_as_well() {
let args = vec![
"unleash-edge",
"edge",
"-u http://localhost:4242",
"--redis-host",
"localhost",
"--redis-username",
"redis",
"--redis-password",
"password",
"--redis-port",
"6389",
"--redis-secure",
];
let args = CliArgs::parse_from(args);
match args.mode {
EdgeMode::Edge(args) => {
let redis_url = args.redis.unwrap().to_url();
assert!(redis_url.is_some());
assert_eq!(redis_url.unwrap(), "rediss://redis:password@localhost:6389");
}
_ => unreachable!(),
}
}
}