Skip to content

Commit bd06fcb

Browse files
Merge pull request #3313 from didier-wenzek/feat/simplify-tedge-multicall
feat: Trigger tedge multicall with symlinks as well as sub-commands
2 parents 3d85d66 + c80b3d0 commit bd06fcb

File tree

5 files changed

+139
-33
lines changed

5 files changed

+139
-33
lines changed

crates/core/tedge/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ license = { workspace = true }
99
homepage = { workspace = true }
1010
repository = { workspace = true }
1111
readme = "README.md"
12+
default-run = "tedge"
1213

1314
[dependencies]
1415
anstyle = { workspace = true }

crates/core/tedge/src/cli/mod.rs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use c8y_firmware_plugin::FirmwarePluginOpt;
77
use c8y_remote_access_plugin::C8yRemoteAccessPluginOpt;
88
pub use connect::*;
99
use tedge_agent::AgentOpt;
10+
use tedge_apt_plugin::AptCli;
1011
use tedge_config::cli::CommonArgs;
1112
use tedge_mapper::MapperOpt;
1213
use tedge_watchdog::WatchdogOpt;
@@ -36,6 +37,7 @@ mod upload;
3637
multicall(true),
3738
)]
3839
pub enum TEdgeOptMulticall {
40+
/// Command line interface to interact with thin-edge.io
3941
Tedge {
4042
#[clap(subcommand)]
4143
cmd: TEdgeOpt,
@@ -50,15 +52,18 @@ pub enum TEdgeOptMulticall {
5052

5153
#[derive(clap::Parser, Debug)]
5254
pub enum Component {
53-
TedgeMapper(MapperOpt),
55+
C8yFirmwarePlugin(FirmwarePluginOpt),
56+
57+
C8yRemoteAccessPlugin(C8yRemoteAccessPluginOpt),
5458

5559
TedgeAgent(AgentOpt),
5660

57-
C8yFirmwarePlugin(FirmwarePluginOpt),
61+
#[clap(alias = "apt")]
62+
TedgeAptPlugin(AptCli),
5863

59-
TedgeWatchdog(WatchdogOpt),
64+
TedgeMapper(MapperOpt),
6065

61-
C8yRemoteAccessPlugin(C8yRemoteAccessPluginOpt),
66+
TedgeWatchdog(WatchdogOpt),
6267

6368
TedgeWrite(TedgeWriteOpt),
6469
}
@@ -113,6 +118,15 @@ pub enum TEdgeOpt {
113118
/// Publish a message on a topic and subscribe a topic.
114119
#[clap(subcommand)]
115120
Mqtt(mqtt::TEdgeMqttCli),
121+
122+
/// Run thin-edge services and plugins
123+
Run(ComponentOpt),
124+
}
125+
126+
#[derive(Debug, clap::Parser)]
127+
pub struct ComponentOpt {
128+
#[clap(subcommand)]
129+
pub component: Component,
116130
}
117131

118132
fn styles() -> clap::builder::Styles {
@@ -174,6 +188,10 @@ impl BuildCommand for TEdgeOpt {
174188
TEdgeOpt::RefreshBridges => RefreshBridgesCmd::new(&context).map(Command::into_boxed),
175189
TEdgeOpt::Mqtt(opt) => opt.build_command(context),
176190
TEdgeOpt::Reconnect(opt) => opt.build_command(context),
191+
TEdgeOpt::Run(_) => {
192+
// This method has to be kept in sync with tedge::redirect_if_multicall()
193+
panic!("tedge mapper|agent|write commands are launched as multicall")
194+
}
177195
}
178196
}
179197
}

crates/core/tedge/src/main.rs

Lines changed: 87 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@ use anyhow::Context;
55
use cap::Cap;
66
use clap::error::ErrorFormatter;
77
use clap::error::RichFormatter;
8+
use clap::CommandFactory;
9+
use clap::FromArgMatches;
810
use clap::Parser;
911
use std::alloc;
12+
use std::ffi::OsString;
1013
use std::future::Future;
1114
use std::io::IsTerminal;
1215
use std::path::PathBuf;
@@ -15,8 +18,11 @@ use tedge::command::BuildCommand;
1518
use tedge::command::BuildContext;
1619
use tedge::log::MaybeFancy;
1720
use tedge::Component;
21+
use tedge::ComponentOpt;
22+
use tedge::TEdgeOpt;
1823
use tedge::TEdgeOptMulticall;
1924
use tedge_apt_plugin::AptCli;
25+
use tedge_config::cli::CommonArgs;
2026
use tedge_config::system_services::log_init;
2127
use tracing::log;
2228

@@ -26,12 +32,7 @@ static ALLOCATOR: Cap<alloc::System> = Cap::new(alloc::System, usize::MAX);
2632
fn main() -> anyhow::Result<()> {
2733
let executable_name = executable_name();
2834

29-
if matches!(executable_name.as_deref(), Some("apt" | "tedge-apt-plugin")) {
30-
let try_opt = AptCli::try_parse();
31-
tedge_apt_plugin::run_and_exit(try_opt);
32-
}
33-
34-
let opt = parse_multicall_if_known(&executable_name);
35+
let opt = parse_multicall(&executable_name, std::env::args_os());
3536
match opt {
3637
TEdgeOptMulticall::Component(Component::TedgeMapper(opt)) => {
3738
let tedge_config = tedge_config::TEdgeConfig::load(&opt.common.config_dir)?;
@@ -58,6 +59,9 @@ fn main() -> anyhow::Result<()> {
5859
block_on(tedge_watchdog::run(opt))
5960
}
6061
TEdgeOptMulticall::Component(Component::TedgeWrite(opt)) => tedge_write::bin::run(opt),
62+
TEdgeOptMulticall::Component(Component::TedgeAptPlugin(opt)) => {
63+
tedge_apt_plugin::run_and_exit(opt)
64+
}
6165
TEdgeOptMulticall::Tedge { cmd, common } => {
6266
let tedge_config_location =
6367
tedge_config::TEdgeConfigLocation::from_custom_root(&common.config_dir);
@@ -120,20 +124,94 @@ fn executable_name() -> Option<String> {
120124
)
121125
}
122126

123-
fn parse_multicall_if_known<T: Parser>(executable_name: &Option<String>) -> T {
124-
let cmd = T::command();
127+
fn parse_multicall<Arg, Args>(executable_name: &Option<String>, args: Args) -> TEdgeOptMulticall
128+
where
129+
Args: IntoIterator<Item = Arg>,
130+
Arg: Into<OsString> + Clone,
131+
{
132+
if matches!(executable_name.as_deref(), Some("apt" | "tedge-apt-plugin")) {
133+
// the apt plugin must be treated apart
134+
// as we want to exit 1 and not 2 when the command line cannot be parsed
135+
match AptCli::try_parse() {
136+
Ok(apt) => return TEdgeOptMulticall::Component(Component::TedgeAptPlugin(apt)),
137+
Err(e) => {
138+
eprintln!("{}", RichFormatter::format_error(&e));
139+
std::process::exit(1);
140+
}
141+
}
142+
}
143+
144+
let cmd = TEdgeOptMulticall::command();
125145

126146
let is_known_subcommand = executable_name
127147
.as_deref()
128148
.map_or(false, |name| cmd.find_subcommand(name).is_some());
129149
let cmd = cmd.multicall(is_known_subcommand);
130150

131151
let cmd2 = cmd.clone();
132-
match T::from_arg_matches(&cmd.get_matches()) {
152+
match TEdgeOptMulticall::from_arg_matches(&cmd.get_matches_from(args)) {
153+
Ok(TEdgeOptMulticall::Tedge { cmd, common }) => redirect_if_multicall(cmd, common),
133154
Ok(t) => t,
134155
Err(e) => {
135156
eprintln!("{}", RichFormatter::format_error(&e.with_cmd(&cmd2)));
136157
std::process::exit(1);
137158
}
138159
}
139160
}
161+
162+
// Transform `tedge mapper|agent|write` commands into multicalls
163+
//
164+
// This method has to be kept in sync with TEdgeOpt::build_command
165+
fn redirect_if_multicall(cmd: TEdgeOpt, common: CommonArgs) -> TEdgeOptMulticall {
166+
match cmd {
167+
TEdgeOpt::Run(ComponentOpt { component }) => TEdgeOptMulticall::Component(component),
168+
cmd => TEdgeOptMulticall::Tedge { cmd, common },
169+
}
170+
}
171+
172+
#[cfg(test)]
173+
mod tests {
174+
use crate::parse_multicall;
175+
use crate::Component;
176+
use crate::TEdgeOptMulticall;
177+
use test_case::test_case;
178+
179+
#[test]
180+
fn launching_a_mapper() {
181+
let exec = Some("tedge-mapper".to_string());
182+
let cmd = parse_multicall(&exec, ["tedge-mapper", "c8y"]);
183+
assert!(matches!(
184+
cmd,
185+
TEdgeOptMulticall::Component(Component::TedgeMapper(_))
186+
))
187+
}
188+
189+
#[test]
190+
fn using_tedge_to_launch_a_mapper() {
191+
let exec = Some("tedge".to_string());
192+
let cmd = parse_multicall(&exec, ["tedge", "run", "tedge-mapper", "c8y"]);
193+
assert!(matches!(
194+
cmd,
195+
TEdgeOptMulticall::Component(Component::TedgeMapper(_))
196+
))
197+
}
198+
199+
#[test_case("tedge-mapper c8y --config-dir /some/dir")]
200+
#[test_case("tedge-mapper --config-dir /some/dir c8y")]
201+
#[test_case("tedge run tedge-mapper c8y --config-dir /some/dir")]
202+
#[test_case("tedge run tedge-mapper --config-dir /some/dir c8y")]
203+
#[test_case("tedge --config-dir /some/dir run tedge-mapper c8y")]
204+
// clap fails to raise an error here and takes the inner value for all global args
205+
#[test_case("tedge --config-dir /oops run tedge-mapper c8y --config-dir /some/dir")]
206+
fn setting_config_dir(cmd_line: &'static str) {
207+
let args: Vec<&str> = cmd_line.split(' ').collect();
208+
let exec = Some(args.get(0).unwrap().to_string());
209+
let cmd = parse_multicall(&exec, args);
210+
match cmd {
211+
TEdgeOptMulticall::Component(Component::TedgeMapper(mapper)) => {
212+
assert_eq!(mapper.common.config_dir, "/some/dir")
213+
}
214+
_ => panic!(),
215+
}
216+
}
217+
}

docs/src/references/cli/index.md

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,11 @@ sidebar_position: 4
77
# The tedge command
88

99
```sh title="tedge"
10-
tedge is the cli tool for thin-edge.io
10+
Command line interface to interact with thin-edge.io
1111

12-
USAGE:
13-
tedge [OPTIONS] [SUBCOMMAND]
12+
Usage: tedge [OPTIONS] <COMMAND>
1413

15-
OPTIONS:
16-
--config-dir <CONFIG_DIR> [env: TEDGE_CONFIG_DIR, default: /etc/tedge]
17-
-h, --help Print help information
18-
--init Initialize the tedge
19-
-V, --version Print version information
20-
21-
SUBCOMMANDS:
14+
Commands:
2215
init Initialize Thin Edge
2316
cert Create and manage device certificate
2417
config Configure Thin Edge
@@ -28,5 +21,30 @@ SUBCOMMANDS:
2821
refresh-bridges Refresh all currently active mosquitto bridges
2922
upload Upload files to the cloud
3023
mqtt Publish a message on a topic and subscribe a topic
24+
run Run thin-edge services and plugins
3125
help Print this message or the help of the given subcommand(s)
26+
27+
Options:
28+
--config-dir <CONFIG_DIR>
29+
[env: TEDGE_CONFIG_DIR, default: /etc/tedge]
30+
31+
--debug
32+
Turn-on the DEBUG log level.
33+
34+
If off only reports ERROR, WARN, and INFO, if on also reports DEBUG
35+
36+
--log-level <LOG_LEVEL>
37+
Configures the logging level.
38+
39+
One of error/warn/info/debug/trace.
40+
Logs with verbosity lower or equal to the selected level will be printed,
41+
i.e. warn prints ERROR and WARN logs and trace prints logs of all levels.
42+
43+
Overrides `--debug`
44+
45+
-h, --help
46+
Print help (see a summary with '-h')
47+
48+
-V, --version
49+
Print version
3250
```

plugins/tedge_apt_plugin/src/lib.rs

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -346,16 +346,7 @@ fn get_config(config_dir: &Path) -> Option<TEdgeConfig> {
346346
}
347347
}
348348

349-
pub fn run_and_exit(cli: Result<AptCli, clap::Error>) -> ! {
350-
let mut apt = match cli {
351-
Ok(aptcli) => aptcli,
352-
Err(err) => {
353-
err.print().expect("Failed to print help message");
354-
// re-write the clap exit_status from 2 to 1, if parse fails
355-
std::process::exit(1)
356-
}
357-
};
358-
349+
pub fn run_and_exit(mut apt: AptCli) -> ! {
359350
if let PluginOp::List { name, maintainer } = &mut apt.operation {
360351
if let Some(config) = get_config(apt.common.config_dir.as_std_path()) {
361352
if name.is_none() {

0 commit comments

Comments
 (0)