diff --git a/Cargo.lock b/Cargo.lock index 486439f995..8c7b7ac26c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5302,7 +5302,6 @@ dependencies = [ "async-trait", "cap-std", "chrono", - "clap", "crossbeam-channel", "futures", "hyper", diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index ac850741eb..6a31fded54 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -16,7 +16,6 @@ doctest = false anyhow = { workspace = true } async-trait = { workspace = true } chrono = { workspace = true } -clap = { workspace = true } prost-types = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } diff --git a/runtime/src/alpha/args.rs b/runtime/src/alpha/args.rs index 21afa20f69..3b482151ca 100644 --- a/runtime/src/alpha/args.rs +++ b/runtime/src/alpha/args.rs @@ -1,37 +1,109 @@ -use std::path::PathBuf; +use std::{ + env, + fmt::{self, Debug, Display}, + path::PathBuf, + str::FromStr, +}; -use clap::{Parser, ValueEnum}; use tonic::transport::{Endpoint, Uri}; -#[derive(Parser, Debug)] -#[command(version)] -pub struct Args { - /// Port to start runtime on - #[arg(long)] - pub port: u16, +macro_rules! arg_or_default { + ($arg:literal, $field:ident $(,)?) => { + $field.ok_or_else(|| Error::MissingRequiredArgument { arg: $arg }) + }; + ($arg:literal, $field:ident, $default:literal) => { + Ok($field.unwrap_or_else(|| $default.parse().unwrap())) + }; +} + +macro_rules! args { + ($($arg:literal => $(#[arg(default_value = $default:literal)])? $field:ident: $ty:ty),+ $(,)?) => { + #[derive(Debug)] + pub struct Args { + $(pub $field: $ty,)+ + } - /// Address to reach provisioner at - #[arg(long, default_value = "http://localhost:5000")] - pub provisioner_address: Endpoint, + impl Args { + pub fn parse() -> Result { + $(let mut $field: Option<$ty> = None;)+ - /// Type of storage manager to start - #[arg(long, value_enum)] - pub storage_manager_type: StorageManagerType, + let mut args_iter = env::args(); + while let Some(arg) = args_iter.next() { + match arg.as_str() { + $($arg => { + if $field.is_some() { + return Err(Error::DuplicatedArgument { arg: $arg }); + } + let raw_value = args_iter + .next() + .ok_or_else(|| Error::MissingValue { arg: $arg })?; + let value = raw_value.parse().map_err(|_| Error::InvalidValue { + arg: $arg, + value: raw_value, + })?; + $field = Some(value); + })+ + _ => return Err(Error::UnexpectedArgument { arg }), + } + } - /// Path to use for storage manager - #[arg(long)] - pub storage_manager_path: PathBuf, + Ok(Args { + $($field: arg_or_default!($arg, $field, $($default)?)?,)+ + }) + } + } + } +} - /// Address to reach the authentication service at - #[arg(long, default_value = "http://127.0.0.1:8008")] - pub auth_uri: Uri, +args! { + "--port" => port: u16, + "--provisioner-address" => #[arg(default_value = "http://localhost:5000")] provisioner_address: Endpoint, + "--storage-manager-type" => storage_manager_type: StorageManagerType, + "--storage-manager-path" => storage_manager_path: PathBuf, + "--auth-uri" => #[arg(default_value = "http://127.0.0.1:8008")] auth_uri: Uri, } -#[derive(Clone, Debug, ValueEnum)] +#[derive(Clone, Debug)] pub enum StorageManagerType { - /// Use a deloyer artifacts directory Artifacts, - - /// Use a local working directory WorkingDir, } + +impl FromStr for StorageManagerType { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "artifacts" => Ok(StorageManagerType::Artifacts), + "working-dir" => Ok(StorageManagerType::WorkingDir), + _ => Err(()), + } + } +} + +#[derive(Debug)] +pub enum Error { + DuplicatedArgument { arg: &'static str }, + MissingRequiredArgument { arg: &'static str }, + UnexpectedArgument { arg: String }, + + InvalidValue { arg: &'static str, value: String }, + MissingValue { arg: &'static str }, +} + +impl Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::DuplicatedArgument { arg } => write!(f, "duplicated argument {arg}"), + Self::MissingRequiredArgument { arg } => write!(f, "missing required argument {arg}"), + Self::UnexpectedArgument { arg } => write!(f, "unexpected argument: {arg}"), + + Self::InvalidValue { arg, value } => { + write!(f, "invalid value for argument {arg}: {value}") + } + Self::MissingValue { arg } => write!(f, "missing value for argument {arg}"), + } + } +} + +impl std::error::Error for Error {} diff --git a/runtime/src/alpha/mod.rs b/runtime/src/alpha/mod.rs index 2c90124dea..d142517a29 100644 --- a/runtime/src/alpha/mod.rs +++ b/runtime/src/alpha/mod.rs @@ -10,7 +10,6 @@ use std::{ use anyhow::Context; use async_trait::async_trait; -use clap::Parser; use core::future::Future; use shuttle_common::{ backends::{ @@ -51,7 +50,7 @@ use self::args::Args; mod args; pub async fn start(loader: impl Loader + Send + 'static) { - let args = Args::parse(); + let args = Args::parse().expect("could not parse arguments"); let addr = SocketAddr::new(Ipv4Addr::LOCALHOST.into(), args.port); let provisioner_address = args.provisioner_address;