Skip to content

Commit 7f1d3da

Browse files
committed
Adds structure to support lading subcommands, 'run' is an alias for the current root cmd
1 parent 9677796 commit 7f1d3da

File tree

2 files changed

+74
-29
lines changed

2 files changed

+74
-29
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1616
- Added a `/proc/vmstat` observer.
1717
## Changed
1818
- 'lading process-tree-gen' command is removed as it is currently unused
19+
- 'lading run' sub-command added as alias for current top-level CLI usage.
1920

2021
## [0.26.0]
2122
## Added

lading/src/bin/lading.rs

Lines changed: 73 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{
77
str::FromStr,
88
};
99

10-
use clap::{ArgGroup, Parser};
10+
use clap::{ArgGroup, Args, Parser, Subcommand};
1111
use jemallocator::Jemalloc;
1212
use lading::{
1313
blackhole,
@@ -125,8 +125,24 @@ impl FromStr for CliKeyValues {
125125
}
126126
}
127127

128+
// Parser for subcommand structure
128129
#[derive(Parser)]
129130
#[clap(version, about, long_about = None)]
131+
struct CliWithSubcommands {
132+
#[command(subcommand)]
133+
command: Commands,
134+
}
135+
136+
// Parser for legacy flat structure (deprecated)
137+
#[derive(Parser)]
138+
#[clap(version, about, long_about = None)]
139+
struct CliFlatLegacy {
140+
#[command(flatten)]
141+
args: LadingArgs,
142+
}
143+
144+
// Shared arguments used by both modes
145+
#[derive(clap::Args)]
130146
#[clap(group(
131147
ArgGroup::new("target")
132148
.required(true)
@@ -142,7 +158,7 @@ impl FromStr for CliKeyValues {
142158
.required(false)
143159
.args(&["experiment_duration_seconds", "experiment_duration_infinite"]),
144160
))]
145-
struct Opts {
161+
struct LadingArgs {
146162
/// path on disk to the configuration file
147163
#[clap(long, default_value_t = default_config_path())]
148164
config_path: String,
@@ -210,7 +226,19 @@ struct Opts {
210226
disable_inspector: bool,
211227
}
212228

213-
fn get_config(ops: &Opts, config: Option<String>) -> Result<Config, Error> {
229+
#[derive(Subcommand)]
230+
enum Commands {
231+
/// Run lading with specified configuration
232+
Run(RunCommand),
233+
}
234+
235+
#[derive(Args)]
236+
struct RunCommand {
237+
#[command(flatten)]
238+
args: LadingArgs,
239+
}
240+
241+
fn get_config(args: &LadingArgs, config: Option<String>) -> Result<Config, Error> {
214242
let contents = if let Some(config) = config {
215243
config
216244
} else if let Ok(env_var_value) = env::var("LADING_CONFIG") {
@@ -219,13 +247,16 @@ fn get_config(ops: &Opts, config: Option<String>) -> Result<Config, Error> {
219247
} else {
220248
debug!(
221249
"Attempting to open configuration file at: {}",
222-
ops.config_path
250+
args.config_path
223251
);
224252
let mut file: std::fs::File = std::fs::OpenOptions::new()
225253
.read(true)
226-
.open(&ops.config_path)
254+
.open(&args.config_path)
227255
.unwrap_or_else(|_| {
228-
panic!("Could not open configuration file at: {}", &ops.config_path)
256+
panic!(
257+
"Could not open configuration file at: {}",
258+
&args.config_path
259+
)
229260
});
230261
let mut contents = String::new();
231262
file.read_to_string(&mut contents)?;
@@ -235,52 +266,52 @@ fn get_config(ops: &Opts, config: Option<String>) -> Result<Config, Error> {
235266

236267
let mut config: Config = serde_yaml::from_str(&contents)?;
237268

238-
let target = if ops.no_target {
269+
let target = if args.no_target {
239270
None
240-
} else if let Some(pid) = ops.target_pid {
271+
} else if let Some(pid) = args.target_pid {
241272
Some(target::Config::Pid(target::PidConfig {
242273
pid: pid.try_into().expect("Could not convert pid to i32"),
243274
}))
244-
} else if let Some(name) = &ops.target_container {
275+
} else if let Some(name) = &args.target_container {
245276
Some(target::Config::Docker(target::DockerConfig {
246277
name: name.clone(),
247278
}))
248-
} else if let Some(path) = &ops.target_path {
279+
} else if let Some(path) = &args.target_path {
249280
Some(target::Config::Binary(target::BinaryConfig {
250281
command: path.clone(),
251-
arguments: ops.target_arguments.clone(),
252-
inherit_environment: ops.target_inherit_environment,
253-
environment_variables: ops
282+
arguments: args.target_arguments.clone(),
283+
inherit_environment: args.target_inherit_environment,
284+
environment_variables: args
254285
.target_environment_variables
255286
.clone()
256287
.unwrap_or_default()
257288
.inner,
258289
output: Output {
259-
stderr: ops.target_stderr_path.clone(),
260-
stdout: ops.target_stdout_path.clone(),
290+
stderr: args.target_stderr_path.clone(),
291+
stdout: args.target_stdout_path.clone(),
261292
},
262293
}))
263294
} else {
264295
unreachable!("clap ensures that exactly one target option is selected");
265296
};
266297
config.target = target;
267298

268-
let options_global_labels = ops.global_labels.clone().unwrap_or_default();
269-
if let Some(ref prom_addr) = ops.prometheus_addr {
299+
let options_global_labels = args.global_labels.clone().unwrap_or_default();
300+
if let Some(ref prom_addr) = args.prometheus_addr {
270301
config.telemetry = Telemetry::Prometheus {
271302
addr: prom_addr.parse()?,
272303
global_labels: options_global_labels.inner,
273304
};
274-
} else if let Some(ref prom_path) = ops.prometheus_path {
305+
} else if let Some(ref prom_path) = args.prometheus_path {
275306
config.telemetry = Telemetry::PrometheusSocket {
276307
path: prom_path.parse().map_err(|_| Error::PrometheusPath)?,
277308
global_labels: options_global_labels.inner,
278309
};
279-
} else if let Some(ref capture_path) = ops.capture_path {
310+
} else if let Some(ref capture_path) = args.capture_path {
280311
config.telemetry = Telemetry::Log {
281312
path: capture_path.parse().map_err(|_| Error::CapturePath)?,
282313
global_labels: options_global_labels.inner,
283-
expiration: Duration::from_secs(ops.capture_expiriation_seconds.unwrap_or(u64::MAX)),
314+
expiration: Duration::from_secs(args.capture_expiriation_seconds.unwrap_or(u64::MAX)),
284315
};
285316
} else {
286317
match config.telemetry {
@@ -572,21 +603,34 @@ fn main() -> Result<(), Error> {
572603

573604
let version = env!("CARGO_PKG_VERSION");
574605
info!("Starting lading {version} run.");
575-
let opts: Opts = Opts::parse();
576606

577-
let config = get_config(&opts, None);
607+
// Two-parser fallback logic until CliFlatLegacy is removed
608+
let args = match CliWithSubcommands::try_parse() {
609+
Ok(cli) => match cli.command {
610+
Commands::Run(run_cmd) => run_cmd.args,
611+
},
612+
Err(_) => {
613+
// Fall back to legacy parsing
614+
match CliFlatLegacy::try_parse() {
615+
Ok(legacy) => legacy.args,
616+
Err(err) => err.exit(),
617+
}
618+
}
619+
};
620+
621+
let config = get_config(&args, None);
578622

579-
let experiment_duration = if opts.experiment_duration_infinite {
623+
let experiment_duration = if args.experiment_duration_infinite {
580624
Duration::MAX
581625
} else {
582-
Duration::from_secs(opts.experiment_duration_seconds.into())
626+
Duration::from_secs(args.experiment_duration_seconds.into())
583627
};
584628

585-
let warmup_duration = Duration::from_secs(opts.warmup_duration_seconds.into());
629+
let warmup_duration = Duration::from_secs(args.warmup_duration_seconds.into());
586630
// The maximum shutdown delay is shared between `inner_main` and this
587631
// function, hence the divide by two.
588-
let max_shutdown_delay = Duration::from_secs(opts.max_shutdown_delay.into());
589-
let disable_inspector = opts.disable_inspector;
632+
let max_shutdown_delay = Duration::from_secs(args.max_shutdown_delay.into());
633+
let disable_inspector = args.disable_inspector;
590634

591635
let runtime = Builder::new_multi_thread()
592636
.enable_io()
@@ -627,8 +671,8 @@ generator: []
627671
let capture_arg = format!("--capture-path={}", capture_path.display());
628672

629673
let args = vec!["lading", "--no-target", capture_arg.as_str()];
630-
let ops: &Opts = &Opts::parse_from(args);
631-
let config = get_config(ops, Some(contents.to_string()));
674+
let legacy_cli = CliFlatLegacy::parse_from(args);
675+
let config = get_config(&legacy_cli.args, Some(contents.to_string()));
632676
let exit_code = inner_main(
633677
Duration::from_millis(2500),
634678
Duration::from_millis(5000),

0 commit comments

Comments
 (0)