Browse Source

Parce the command line arguments

ng
Rodolphe Bréard 2 months ago
parent
commit
dee7dbec8f
Failed to extract signature
  1. 157
      Cargo.lock
  2. 1
      Cargo.toml
  3. 259
      src/cli.rs
  4. 30
      src/log.rs
  5. 21
      src/main.rs

157
Cargo.lock

@ -6,6 +6,7 @@ version = 3
name = "acmed" name = "acmed"
version = "0.25.0-dev" version = "0.25.0-dev"
dependencies = [ dependencies = [
"clap",
"tokio", "tokio",
"tracing", "tracing",
"tracing-subscriber", "tracing-subscriber",
@ -26,6 +27,55 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "anstream"
version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]]
name = "anstyle"
version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
dependencies = [
"windows-sys",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125"
dependencies = [
"anstyle",
"windows-sys",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.74" version = "0.3.74"
@ -47,12 +97,69 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84"
dependencies = [
"clap_builder",
"clap_derive",
]
[[package]]
name = "clap_builder"
version = "4.5.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
]
[[package]]
name = "clap_derive"
version = "4.5.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]] [[package]]
name = "gimli" name = "gimli"
version = "0.31.1" version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "is_terminal_polyfill"
version = "1.70.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
[[package]] [[package]]
name = "lazy_static" name = "lazy_static"
version = "1.5.0" version = "1.5.0"
@ -117,6 +224,24 @@ version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff"
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]] [[package]]
name = "rustc-demangle" name = "rustc-demangle"
version = "0.1.24" version = "0.1.24"
@ -132,6 +257,17 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.8" version = "1.1.8"
@ -183,6 +319,18 @@ dependencies = [
"tracing-core", "tracing-core",
] ]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "winapi" name = "winapi"
version = "0.3.9" version = "0.3.9"
@ -205,6 +353,15 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"

1
Cargo.toml

@ -22,6 +22,7 @@ ed25519 = []
ed448 = [] ed448 = []
[dependencies] [dependencies]
clap = { version = "4.5.23", default-features = false, features = ["color", "derive", "help", "std"] }
tokio = { version = "1.42.0", default-features = false, features = ["rt", "rt-multi-thread"] } tokio = { version = "1.42.0", default-features = false, features = ["rt", "rt-multi-thread"] }
tracing = { version = "0.1.41", default-features = false, features = ["std"] } tracing = { version = "0.1.41", default-features = false, features = ["std"] }
tracing-subscriber = { version = "0.3.19", default-features = false, features = ["ansi", "fmt", "std"] } tracing-subscriber = { version = "0.3.19", default-features = false, features = ["ansi", "fmt", "std"] }

259
src/cli.rs

@ -0,0 +1,259 @@
use crate::log::Level;
use clap::{Args, Parser};
use std::path::{Path, PathBuf};
#[derive(Parser, Debug)]
#[command(version, about, long_about = None)]
pub struct CliArgs {
/// Path to the main configuration file
#[arg(short, long, value_name = "FILE", default_value = crate::DEFAULT_CONFIG_PATH)]
pub config: PathBuf,
/// Specify the log level
#[arg(long, value_name = "LEVEL", value_enum, default_value_t = crate::DEFAULT_LOG_LEVEL)]
pub log_level: Level,
#[command(flatten)]
pub log: Log,
/// Runs in the foreground
#[arg(short, long, default_value_t = false)]
pub foreground: bool,
#[command(flatten)]
pub pid: Pid,
/// Add a root certificate to the trust store (can be set multiple times)
#[arg(long, value_name = "FILE")]
pub root_cert: Vec<PathBuf>,
}
#[derive(Args, Debug)]
#[group(multiple = false)]
pub struct Log {
/// Sends log messages via syslog
#[arg(long)]
pub log_syslog: bool,
/// Prints log messages to the standard error output
#[arg(long)]
pub log_stderr: bool,
}
#[derive(Args, Debug)]
#[group(multiple = false)]
pub struct Pid {
/// Path to the PID file
#[arg(long, value_name = "FILE", default_value = crate::DEFAULT_PID_FILE)]
pid_file: PathBuf,
/// Do not create any PID file
#[arg(long)]
no_pid_file: bool,
}
impl Pid {
pub fn get_pid_file(&self) -> Option<&Path> {
if !self.no_pid_file {
Some(self.pid_file.as_path())
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::CommandFactory;
#[test]
fn verify_cli() {
CliArgs::command().debug_assert();
}
#[test]
fn no_args() {
let args: &[&str] = &[];
let pa = CliArgs::try_parse_from(args).unwrap();
assert_eq!(pa.config, PathBuf::from(crate::DEFAULT_CONFIG_PATH));
assert_eq!(pa.log_level, Level::Warn);
assert_eq!(pa.log.log_syslog, false);
assert_eq!(pa.log.log_stderr, false);
assert_eq!(pa.foreground, false);
assert_eq!(pa.pid.pid_file, PathBuf::from(crate::DEFAULT_PID_FILE));
assert_eq!(pa.pid.no_pid_file, false);
assert_eq!(
pa.pid.get_pid_file(),
Some(PathBuf::from(crate::DEFAULT_PID_FILE).as_path())
);
assert!(pa.root_cert.is_empty());
}
#[test]
fn all_args_long_1() {
let argv: &[&str] = &[
"acmed",
"--config",
"/tmp/test.toml",
"--log-level",
"debug",
"--log-syslog",
"--foreground",
"--pid-file",
"/tmp/debug/acmed.pid",
"--root-cert",
"/tmp/certs/root_01.pem",
"--root-cert",
"/tmp/certs/root_02.pem",
"--root-cert",
"/tmp/certs/root_03.pem",
];
let pa = CliArgs::try_parse_from(argv).unwrap();
assert_eq!(pa.config, PathBuf::from("/tmp/test.toml"));
assert_eq!(pa.log_level, Level::Debug);
assert_eq!(pa.log.log_syslog, true);
assert_eq!(pa.log.log_stderr, false);
assert_eq!(pa.foreground, true);
assert_eq!(
pa.pid.get_pid_file(),
Some(PathBuf::from("/tmp/debug/acmed.pid").as_path())
);
assert_eq!(
pa.root_cert,
vec![
PathBuf::from("/tmp/certs/root_01.pem"),
PathBuf::from("/tmp/certs/root_02.pem"),
PathBuf::from("/tmp/certs/root_03.pem")
]
);
}
#[test]
fn all_args_long_2() {
let argv: &[&str] = &[
"acmed",
"--config",
"/tmp/test.toml",
"--log-level",
"debug",
"--log-stderr",
"--foreground",
"--no-pid-file",
"--root-cert",
"/tmp/certs/root_01.pem",
"--root-cert",
"/tmp/certs/root_02.pem",
"--root-cert",
"/tmp/certs/root_03.pem",
];
let pa = CliArgs::try_parse_from(argv).unwrap();
assert_eq!(pa.config, PathBuf::from("/tmp/test.toml"));
assert_eq!(pa.log_level, Level::Debug);
assert_eq!(pa.log.log_syslog, false);
assert_eq!(pa.log.log_stderr, true);
assert_eq!(pa.foreground, true);
assert_eq!(pa.pid.get_pid_file(), None);
assert_eq!(
pa.root_cert,
vec![
PathBuf::from("/tmp/certs/root_01.pem"),
PathBuf::from("/tmp/certs/root_02.pem"),
PathBuf::from("/tmp/certs/root_03.pem")
]
);
}
#[test]
fn all_args_short_1() {
let argv: &[&str] = &[
"acmed",
"-c",
"/tmp/test.toml",
"--log-level",
"debug",
"--log-syslog",
"-f",
"--pid-file",
"/tmp/debug/acmed.pid",
"--root-cert",
"/tmp/certs/root_01.pem",
"--root-cert",
"/tmp/certs/root_02.pem",
"--root-cert",
"/tmp/certs/root_03.pem",
];
let pa = CliArgs::try_parse_from(argv).unwrap();
assert_eq!(pa.config, PathBuf::from("/tmp/test.toml"));
assert_eq!(pa.log_level, Level::Debug);
assert_eq!(pa.log.log_syslog, true);
assert_eq!(pa.log.log_stderr, false);
assert_eq!(pa.foreground, true);
assert_eq!(
pa.pid.get_pid_file(),
Some(PathBuf::from("/tmp/debug/acmed.pid").as_path())
);
assert_eq!(
pa.root_cert,
vec![
PathBuf::from("/tmp/certs/root_01.pem"),
PathBuf::from("/tmp/certs/root_02.pem"),
PathBuf::from("/tmp/certs/root_03.pem")
]
);
}
#[test]
fn all_args_short_2() {
let argv: &[&str] = &[
"acmed",
"-c",
"/tmp/test.toml",
"--log-level",
"debug",
"--log-stderr",
"-f",
"--no-pid-file",
"--root-cert",
"/tmp/certs/root_01.pem",
"--root-cert",
"/tmp/certs/root_02.pem",
"--root-cert",
"/tmp/certs/root_03.pem",
];
let pa = CliArgs::try_parse_from(argv).unwrap();
assert_eq!(pa.config, PathBuf::from("/tmp/test.toml"));
assert_eq!(pa.log_level, Level::Debug);
assert_eq!(pa.log.log_syslog, false);
assert_eq!(pa.log.log_stderr, true);
assert_eq!(pa.foreground, true);
assert_eq!(pa.pid.get_pid_file(), None);
assert_eq!(
pa.root_cert,
vec![
PathBuf::from("/tmp/certs/root_01.pem"),
PathBuf::from("/tmp/certs/root_02.pem"),
PathBuf::from("/tmp/certs/root_03.pem")
]
);
}
#[test]
fn err_log_output() {
let argv: &[&str] = &["acmed", "--log-stderr", "--log-syslog"];
let pa = CliArgs::try_parse_from(argv);
assert!(pa.is_err());
}
#[test]
fn err_pid_file() {
let argv: &[&str] = &[
"acmed",
"--pid-file",
"/tmp/debug/acmed.pid",
"--no-pid-file",
];
let pa = CliArgs::try_parse_from(argv);
assert!(pa.is_err());
}
}

30
src/log.rs

@ -0,0 +1,30 @@
use clap::ValueEnum;
use tracing_subscriber::FmtSubscriber;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, ValueEnum)]
pub enum Level {
Error,
Warn,
Info,
Debug,
Trace,
}
impl Level {
fn tracing(&self) -> tracing::Level {
match self {
Self::Error => tracing::Level::ERROR,
Self::Warn => tracing::Level::WARN,
Self::Info => tracing::Level::INFO,
Self::Debug => tracing::Level::DEBUG,
Self::Trace => tracing::Level::TRACE,
}
}
}
pub fn init(level: Level, is_syslog: bool) {
let subscriber = FmtSubscriber::builder()
.with_max_level(level.tracing())
.finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
}

21
src/main.rs

@ -1,13 +1,22 @@
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
mod cli;
mod log;
use clap::Parser;
pub const APP_THREAD_NAME: &str = "acmed-runtime"; pub const APP_THREAD_NAME: &str = "acmed-runtime";
pub const DEFAULT_CONFIG_PATH: &str = "/etc/acmed/acmed.toml";
pub const DEFAULT_LOG_LEVEL: log::Level = log::Level::Warn;
pub const DEFAULT_PID_FILE: &str = "/run/acmed.pid";
fn main() { fn main() {
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.finish();
tracing::subscriber::set_global_default(subscriber).expect("setting default subscriber failed");
// CLI
let args = cli::CliArgs::parse();
println!("Debug: args: {args:?}");
// Initialize the logging system
log::init(args.log_level, args.log.log_syslog);
// Starting ACMEd
tokio::runtime::Builder::new_multi_thread() tokio::runtime::Builder::new_multi_thread()
.enable_all() .enable_all()
.thread_name(APP_THREAD_NAME) .thread_name(APP_THREAD_NAME)

Loading…
Cancel
Save