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