easytier-cli部分命令支持json输出 (#882)
Some checks failed
EasyTier Core / pre_job (push) Has been cancelled
EasyTier GUI / pre_job (push) Has been cancelled
EasyTier Mobile / pre_job (push) Has been cancelled
EasyTier Test / pre_job (push) Has been cancelled
EasyTier Core / build_web (push) Has been cancelled
EasyTier Core / build (freebsd-13.2-x86_64, 13.2, ubuntu-22.04, x86_64-unknown-freebsd) (push) Has been cancelled
EasyTier Core / build (linux-aarch64, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-arm, ubuntu-22.04, arm-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armhf, ubuntu-22.04, arm-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-armv7, ubuntu-22.04, armv7-unknown-linux-musleabi) (push) Has been cancelled
EasyTier Core / build (linux-armv7hf, ubuntu-22.04, armv7-unknown-linux-musleabihf) (push) Has been cancelled
EasyTier Core / build (linux-mips, ubuntu-22.04, mips-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-mipsel, ubuntu-22.04, mipsel-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (linux-x86_64, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier Core / build (macos-aarch64, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (macos-x86_64, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier Core / build (windows-arm64, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-i686, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / build (windows-x86_64, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier Core / core-result (push) Has been cancelled
EasyTier Core / magisk_build (push) Has been cancelled
EasyTier GUI / build-gui (linux-aarch64, aarch64-unknown-linux-gnu, ubuntu-22.04, aarch64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (linux-x86_64, x86_64-unknown-linux-gnu, ubuntu-22.04, x86_64-unknown-linux-musl) (push) Has been cancelled
EasyTier GUI / build-gui (macos-aarch64, aarch64-apple-darwin, macos-latest, aarch64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (macos-x86_64, x86_64-apple-darwin, macos-latest, x86_64-apple-darwin) (push) Has been cancelled
EasyTier GUI / build-gui (windows-arm64, aarch64-pc-windows-msvc, windows-latest, aarch64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-i686, i686-pc-windows-msvc, windows-latest, i686-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / build-gui (windows-x86_64, x86_64-pc-windows-msvc, windows-latest, x86_64-pc-windows-msvc) (push) Has been cancelled
EasyTier GUI / gui-result (push) Has been cancelled
EasyTier Mobile / build-mobile (android, ubuntu-22.04, android) (push) Has been cancelled
EasyTier Mobile / mobile-result (push) Has been cancelled
EasyTier Test / test (push) Has been cancelled

* add cli options to json output
* add cli verbose output in json format for some sub command

- easytier-cli -v peer list
- easytier-cli -v peer list-foreign
- easytier-cli -o json peer list-foreign
- easytier-cli -v peer list-global-foreign
- easytier-cli -o json peer list-global-foreign
- easytier-cli -v route list
- easytier-cli -v connector
- easytier-cli -o json connector
- easytier-cli -o json stun
- easytier-cli -v proxy
- easytier-cli -v node info

---------

Co-authored-by: xzzpig <w2xzzig@hotmail.com>
This commit is contained in:
Mg Pig
2025-05-25 23:28:12 +08:00
committed by GitHub
parent b0fd37949a
commit d7c3179c6e

View File

@@ -3,12 +3,14 @@ use std::{
fmt::Write,
net::{IpAddr, SocketAddr},
path::PathBuf,
str::FromStr,
sync::Mutex,
time::Duration,
vec,
};
use anyhow::Context;
use cidr::Ipv4Inet;
use clap::{command, Args, Parser, Subcommand};
use humansize::format_size;
use service_manager::*;
@@ -51,6 +53,15 @@ struct Cli {
#[arg(short, long, default_value = "false", help = "verbose output")]
verbose: bool,
#[arg(
short = 'o',
long = "output",
value_enum,
default_value = "table",
help = "output format"
)]
output_format: OutputFormat,
#[command(subcommand)]
sub_command: SubCommand,
}
@@ -77,23 +88,23 @@ enum SubCommand {
Proxy,
}
#[derive(clap::ValueEnum, Debug, Clone, PartialEq)]
enum OutputFormat {
Table,
Json,
}
#[derive(Args, Debug)]
struct PeerArgs {
#[command(subcommand)]
sub_command: Option<PeerSubCommand>,
}
#[derive(Args, Debug)]
struct PeerListArgs {
#[arg(short, long)]
verbose: bool,
}
#[derive(Subcommand, Debug)]
enum PeerSubCommand {
Add,
Remove,
List(PeerListArgs),
List,
ListForeign,
ListGlobalForeign,
}
@@ -193,14 +204,15 @@ struct InstallArgs {
type Error = anyhow::Error;
struct CommandHandler {
struct CommandHandler<'a> {
client: Mutex<RpcClient>,
verbose: bool,
output_format: &'a OutputFormat,
}
type RpcClient = StandAloneClient<TcpTunnelConnector>;
impl CommandHandler {
impl CommandHandler<'_> {
async fn get_peer_manager_client(
&self,
) -> Result<Box<dyn PeerManageRpc<Controller = BaseController>>, Error> {
@@ -294,9 +306,12 @@ impl CommandHandler {
println!("remove peer");
}
async fn handle_peer_list(&self, _args: &PeerArgs) -> Result<(), Error> {
#[derive(tabled::Tabled)]
async fn handle_peer_list(&self) -> Result<(), Error> {
#[derive(tabled::Tabled, serde::Serialize)]
struct PeerTableItem {
#[tabled(rename = "ipv4")]
cidr: String,
#[tabled(skip)]
ipv4: String,
hostname: String,
cost: String,
@@ -314,7 +329,12 @@ impl CommandHandler {
fn from(p: PeerRoutePair) -> Self {
let route = p.route.clone().unwrap_or_default();
PeerTableItem {
ipv4: route.ipv4_addr.map(|ip| ip.to_string()).unwrap_or_default(),
cidr: route.ipv4_addr.map(|ip| ip.to_string()).unwrap_or_default(),
ipv4: route
.ipv4_addr
.map(|ip: easytier::proto::common::Ipv4Inet| ip.address.unwrap_or_default())
.map(|ip| ip.to_string())
.unwrap_or_default(),
hostname: route.hostname.clone(),
cost: cost_to_str(route.cost),
lat_ms: if route.cost == 1 {
@@ -344,7 +364,10 @@ impl CommandHandler {
impl From<NodeInfo> for PeerTableItem {
fn from(p: NodeInfo) -> Self {
PeerTableItem {
ipv4: p.ipv4_addr.clone(),
cidr: p.ipv4_addr.clone(),
ipv4: Ipv4Inet::from_str(&p.ipv4_addr)
.map(|ip| ip.address().to_string())
.unwrap_or_default(),
hostname: p.hostname.clone(),
cost: "Local".to_string(),
lat_ms: "-".to_string(),
@@ -366,7 +389,7 @@ impl CommandHandler {
let mut items: Vec<PeerTableItem> = vec![];
let peer_routes = self.list_peer_route_pair().await?;
if self.verbose {
println!("{:#?}", peer_routes);
println!("{}", serde_json::to_string_pretty(&peer_routes)?);
return Ok(());
}
@@ -382,7 +405,7 @@ impl CommandHandler {
items.push(p.into());
}
println!("{}", tabled::Table::new(items).with(Style::modern()));
print_output(&items, self.output_format)?;
Ok(())
}
@@ -404,8 +427,9 @@ impl CommandHandler {
.list_foreign_network(BaseController::default(), request)
.await?;
let network_map = response;
if self.verbose {
println!("{:#?}", network_map);
if self.verbose || *self.output_format == OutputFormat::Json {
let json = serde_json::to_string_pretty(&network_map.foreign_networks)?;
println!("{}", json);
return Ok(());
}
@@ -445,8 +469,11 @@ impl CommandHandler {
let response = client
.list_global_foreign_network(BaseController::default(), request)
.await?;
if self.verbose {
println!("{:#?}", response);
if self.verbose || *self.output_format == OutputFormat::Json {
println!(
"{}",
serde_json::to_string_pretty(&response.foreign_networks)?
);
return Ok(());
}
@@ -464,7 +491,7 @@ impl CommandHandler {
}
async fn handle_route_list(&self) -> Result<(), Error> {
#[derive(tabled::Tabled)]
#[derive(tabled::Tabled, serde::Serialize)]
struct RouteTableItem {
ipv4: String,
hostname: String,
@@ -491,6 +518,23 @@ impl CommandHandler {
.await?
.node_info
.ok_or(anyhow::anyhow!("node info not found"))?;
let peer_routes = self.list_peer_route_pair().await?;
if self.verbose {
#[derive(serde::Serialize)]
struct VerboseItem {
node_info: NodeInfo,
peer_routes: Vec<PeerRoutePair>,
}
println!(
"{}",
serde_json::to_string_pretty(&VerboseItem {
node_info,
peer_routes
})?
);
return Ok(());
}
items.push(RouteTableItem {
ipv4: node_info.ipv4_addr.clone(),
@@ -510,7 +554,6 @@ impl CommandHandler {
version: node_info.version.clone(),
});
let peer_routes = self.list_peer_route_pair().await?;
for p in peer_routes.iter() {
let Some(next_hop_pair) = peer_routes.iter().find(|pair| {
pair.route.clone().unwrap_or_default().peer_id
@@ -634,7 +677,7 @@ impl CommandHandler {
}
}
println!("{}", tabled::Table::new(items).with(Style::modern()));
print_output(&items, self.output_format)?;
Ok(())
}
@@ -645,6 +688,10 @@ impl CommandHandler {
let response = client
.list_connector(BaseController::default(), request)
.await?;
if self.verbose || *self.output_format == OutputFormat::Json {
println!("{}", serde_json::to_string_pretty(&response.connectors)?);
return Ok(());
}
println!("response: {:#?}", response);
Ok(())
}
@@ -912,6 +959,21 @@ impl Service {
}
}
fn print_output<T>(items: &[T], format: &OutputFormat) -> Result<(), Error>
where
T: tabled::Tabled + serde::Serialize,
{
match format {
OutputFormat::Table => {
println!("{}", tabled::Table::new(items).with(Style::modern()));
}
OutputFormat::Json => {
println!("{}", serde_json::to_string_pretty(items)?);
}
}
Ok(())
}
#[tokio::main]
#[tracing::instrument]
async fn main() -> Result<(), Error> {
@@ -924,6 +986,7 @@ async fn main() -> Result<(), Error> {
let handler = CommandHandler {
client: Mutex::new(client),
verbose: cli.verbose,
output_format: &cli.output_format,
};
match cli.sub_command {
@@ -934,12 +997,8 @@ async fn main() -> Result<(), Error> {
Some(PeerSubCommand::Remove) => {
println!("remove peer");
}
Some(PeerSubCommand::List(arg)) => {
if arg.verbose {
println!("{:#?}", handler.list_peer_route_pair().await?);
} else {
handler.handle_peer_list(&peer_args).await?;
}
Some(PeerSubCommand::List) => {
handler.handle_peer_list().await?;
}
Some(PeerSubCommand::ListForeign) => {
handler.handle_foreign_network_list().await?;
@@ -948,7 +1007,7 @@ async fn main() -> Result<(), Error> {
handler.handle_global_foreign_network_list().await?;
}
None => {
handler.handle_peer_list(&peer_args).await?;
handler.handle_peer_list().await?;
}
},
SubCommand::Connector(conn_args) => match conn_args.sub_command {
@@ -975,7 +1034,14 @@ async fn main() -> Result<(), Error> {
loop {
let ret = collector.get_stun_info();
if ret.udp_nat_type != NatType::Unknown as i32 {
println!("stun info: {:#?}", ret);
if cli.output_format == OutputFormat::Json {
match serde_json::to_string_pretty(&ret) {
Ok(json) => println!("{}", json),
Err(e) => eprintln!("Error serializing to JSON: {}", e),
}
} else {
println!("stun info: {:#?}", ret);
}
break;
}
tokio::time::sleep(Duration::from_millis(200)).await;
@@ -993,27 +1059,45 @@ async fn main() -> Result<(), Error> {
)
.await?;
#[derive(tabled::Tabled)]
#[derive(tabled::Tabled, serde::Serialize)]
struct PeerCenterTableItem {
node_id: String,
direct_peers: String,
#[tabled(rename = "direct_peers")]
#[serde(skip_serializing)]
direct_peers_str: String,
#[tabled(skip)]
direct_peers: Vec<DirectPeerItem>,
}
#[derive(serde::Serialize)]
struct DirectPeerItem {
node_id: String,
latency_ms: i32,
}
let mut table_rows = vec![];
for (k, v) in resp.global_peer_map.iter() {
let node_id = k;
let direct_peers = v
let direct_peers_strs = v
.direct_peers
.iter()
.map(|(k, v)| format!("{}: {:?}ms", k, v.latency_ms,))
.collect::<Vec<_>>();
let direct_peers: Vec<_> = v.direct_peers
.iter()
.map(|(k, v)| DirectPeerItem {
node_id: k.to_string(),
latency_ms: v.latency_ms,
})
.collect();
table_rows.push(PeerCenterTableItem {
node_id: node_id.to_string(),
direct_peers: direct_peers.join("\n"),
direct_peers_str: direct_peers_strs.join("\n"),
direct_peers,
});
}
println!("{}", tabled::Table::new(table_rows).with(Style::modern()));
print_output(&table_rows, &cli.output_format)?;
}
SubCommand::VpnPortal => {
let vpn_portal_client = handler.get_vpn_portal_client().await?;
@@ -1045,6 +1129,11 @@ async fn main() -> Result<(), Error> {
.ok_or(anyhow::anyhow!("node info not found"))?;
match sub_cmd.sub_command {
Some(NodeSubCommand::Info) | None => {
if cli.verbose || cli.output_format == OutputFormat::Json {
println!("{}", serde_json::to_string_pretty(&node_info)?);
return Ok(());
}
let stun_info = node_info.stun_info.clone().unwrap_or_default();
let ip_list = node_info.ip_list.clone().unwrap_or_default();
@@ -1186,7 +1275,12 @@ async fn main() -> Result<(), Error> {
.await;
entries.extend(ret.unwrap_or_default().entries);
#[derive(tabled::Tabled)]
if cli.verbose {
println!("{}", serde_json::to_string_pretty(&entries)?);
return Ok(());
}
#[derive(tabled::Tabled, serde::Serialize)]
struct TableItem {
src: String,
dst: String,
@@ -1215,7 +1309,7 @@ async fn main() -> Result<(), Error> {
})
.collect::<Vec<_>>();
println!("{}", tabled::Table::new(table_rows).with(Style::modern()));
print_output(&table_rows, &cli.output_format)?;
}
}