use std::path::PathBuf;
use clap::{Parser, ValueEnum};
use fakecloud_core::auth::IamMode;
use fakecloud_persistence::{PersistenceConfig, StorageMode};
#[derive(Clone, Copy, Debug, ValueEnum)]
#[clap(rename_all = "lowercase")]
pub(crate) enum IamModeArg {
Off,
Soft,
Strict,
}
impl From<IamModeArg> for IamMode {
fn from(value: IamModeArg) -> Self {
match value {
IamModeArg::Off => IamMode::Off,
IamModeArg::Soft => IamMode::Soft,
IamModeArg::Strict => IamMode::Strict,
}
}
}
#[derive(Clone, Copy, Debug, ValueEnum)]
#[clap(rename_all = "lowercase")]
pub(crate) enum StorageModeArg {
Memory,
Persistent,
}
impl From<StorageModeArg> for StorageMode {
fn from(value: StorageModeArg) -> Self {
match value {
StorageModeArg::Memory => StorageMode::Memory,
StorageModeArg::Persistent => StorageMode::Persistent,
}
}
}
const DEFAULT_S3_CACHE_BYTES: u64 = 256 * 1024 * 1024;
#[derive(Parser)]
#[command(name = "fakecloud")]
#[command(about = "FakeCloud — local AWS cloud emulator")]
#[command(version)]
pub(crate) struct Cli {
#[arg(long, default_value = "0.0.0.0:4566", env = "FAKECLOUD_ADDR")]
pub addr: String,
#[arg(long, default_value = "us-east-1", env = "FAKECLOUD_REGION")]
pub region: String,
#[arg(long, default_value = "123456789012", env = "FAKECLOUD_ACCOUNT_ID")]
pub account_id: String,
#[arg(long, default_value = "info", env = "FAKECLOUD_LOG")]
pub log_level: String,
#[arg(
long,
value_enum,
default_value_t = StorageModeArg::Memory,
env = "FAKECLOUD_STORAGE_MODE",
)]
pub storage_mode: StorageModeArg,
#[arg(long, env = "FAKECLOUD_DATA_PATH")]
pub data_path: Option<PathBuf>,
#[arg(long, default_value_t = DEFAULT_S3_CACHE_BYTES, env = "FAKECLOUD_S3_CACHE_SIZE")]
pub s3_cache_size: u64,
#[arg(long, default_value_t = false, env = "FAKECLOUD_VERIFY_SIGV4")]
pub verify_sigv4: bool,
#[arg(
long = "iam",
value_enum,
default_value_t = IamModeArg::Off,
env = "FAKECLOUD_IAM",
)]
pub iam_mode: IamModeArg,
#[command(subcommand)]
pub command: Option<Command>,
}
#[derive(clap::Subcommand)]
pub(crate) enum Command {
Healthcheck,
}
impl Cli {
pub fn iam_mode(&self) -> IamMode {
self.iam_mode.into()
}
pub fn persistence_config(&self) -> Result<PersistenceConfig, String> {
let mode: StorageMode = self.storage_mode.into();
let config = PersistenceConfig {
mode,
data_path: self.data_path.clone(),
s3_cache_bytes: self.s3_cache_size,
};
config.validate()?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[test]
fn defaults_leave_security_features_off() {
let cli = Cli::try_parse_from(["fakecloud"]).unwrap();
assert!(!cli.verify_sigv4);
assert_eq!(cli.iam_mode(), IamMode::Off);
}
#[test]
fn verify_sigv4_flag_parses() {
let cli = Cli::try_parse_from(["fakecloud", "--verify-sigv4"]).unwrap();
assert!(cli.verify_sigv4);
}
#[test]
fn iam_flag_parses_all_variants() {
let cli = Cli::try_parse_from(["fakecloud", "--iam", "off"]).unwrap();
assert_eq!(cli.iam_mode(), IamMode::Off);
let cli = Cli::try_parse_from(["fakecloud", "--iam", "soft"]).unwrap();
assert_eq!(cli.iam_mode(), IamMode::Soft);
let cli = Cli::try_parse_from(["fakecloud", "--iam", "strict"]).unwrap();
assert_eq!(cli.iam_mode(), IamMode::Strict);
}
#[test]
fn iam_flag_rejects_garbage() {
assert!(Cli::try_parse_from(["fakecloud", "--iam", "allow"]).is_err());
}
#[test]
fn iam_mode_arg_conversion_covers_all_variants() {
assert_eq!(IamMode::from(IamModeArg::Off), IamMode::Off);
assert_eq!(IamMode::from(IamModeArg::Soft), IamMode::Soft);
assert_eq!(IamMode::from(IamModeArg::Strict), IamMode::Strict);
}
#[test]
fn storage_mode_arg_conversion_covers_all_variants() {
assert!(matches!(
StorageMode::from(StorageModeArg::Memory),
StorageMode::Memory
));
assert!(matches!(
StorageMode::from(StorageModeArg::Persistent),
StorageMode::Persistent
));
}
#[test]
fn persistence_config_memory_ok_without_data_path() {
let cli = Cli::try_parse_from(["fakecloud"]).unwrap();
let cfg = cli.persistence_config().unwrap();
assert!(matches!(cfg.mode, StorageMode::Memory));
}
#[test]
fn persistence_config_persistent_requires_data_path() {
let cli = Cli::try_parse_from(["fakecloud", "--storage-mode", "persistent"]).unwrap();
assert!(cli.persistence_config().is_err());
}
#[test]
fn persistence_config_persistent_with_data_path() {
let cli = Cli::try_parse_from([
"fakecloud",
"--storage-mode",
"persistent",
"--data-path",
"/tmp/fc-test",
])
.unwrap();
let cfg = cli.persistence_config().unwrap();
assert!(matches!(cfg.mode, StorageMode::Persistent));
assert_eq!(
cfg.data_path.as_deref(),
Some(std::path::Path::new("/tmp/fc-test"))
);
}
#[test]
fn s3_cache_size_default_and_override() {
let cli = Cli::try_parse_from(["fakecloud"]).unwrap();
assert_eq!(cli.s3_cache_size, DEFAULT_S3_CACHE_BYTES);
let cli = Cli::try_parse_from(["fakecloud", "--s3-cache-size", "1024"]).unwrap();
assert_eq!(cli.s3_cache_size, 1024);
}
}