diff --git a/implementations/rust/ockam/ockam_api/src/util.rs b/implementations/rust/ockam/ockam_api/src/util.rs index 3a64158b404..38d176b7989 100644 --- a/implementations/rust/ockam/ockam_api/src/util.rs +++ b/implementations/rust/ockam/ockam_api/src/util.rs @@ -1,4 +1,5 @@ use miette::miette; +use serde::Serializer; use std::net::{SocketAddrV4, SocketAddrV6}; use ockam::TcpTransport; @@ -307,6 +308,15 @@ pub fn addr_to_multiaddr>(a: T) -> Option { route_to_multiaddr(&r) } +/// Serialize the field attempting to convert it first to a MultiAddr. +pub fn serialize_as_multiaddr(t: &T, s: S) -> Result +where + T: Into
+ Clone, + S: Serializer, +{ + s.serialize_some(&addr_to_multiaddr(t.clone())) +} + /// Tells whether the input MultiAddr references a local node or a remote node. /// /// This should be called before cleaning the MultiAddr. diff --git a/implementations/rust/ockam/ockam_command/src/node/mod.rs b/implementations/rust/ockam/ockam_command/src/node/mod.rs index d06d9574bd9..7c39760f4f0 100644 --- a/implementations/rust/ockam/ockam_command/src/node/mod.rs +++ b/implementations/rust/ockam/ockam_command/src/node/mod.rs @@ -18,6 +18,7 @@ mod default; mod delete; mod list; mod logs; +mod models; mod show; mod start; mod stop; diff --git a/implementations/rust/ockam/ockam_command/src/node/models/mod.rs b/implementations/rust/ockam/ockam_command/src/node/models/mod.rs new file mode 100644 index 00000000000..62f9f1f3acb --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/node/models/mod.rs @@ -0,0 +1,5 @@ +pub mod portal; +pub mod secure_channel; +pub mod services; +pub mod show; +pub mod transport; diff --git a/implementations/rust/ockam/ockam_command/src/node/models/portal.rs b/implementations/rust/ockam/ockam_command/src/node/models/portal.rs new file mode 100644 index 00000000000..d5f60134e33 --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/node/models/portal.rs @@ -0,0 +1,44 @@ +use std::net::SocketAddr; + +use ockam_api::{ + addr_to_multiaddr, + nodes::models::portal::{InletStatus, OutletStatus}, + route_to_multiaddr, +}; +use ockam_core::Route; +use ockam_multiaddr::MultiAddr; +use serde::Serialize; + +/// Information to display of the inlets in the `ockam node show` command +#[derive(Debug, Serialize)] +pub struct ShowInletStatus { + pub listen_address: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub route_to_outlet: Option, +} + +impl From for ShowInletStatus { + fn from(value: InletStatus) -> Self { + Self { + listen_address: value.bind_addr, + route_to_outlet: Route::parse(value.outlet_route).and_then(|r| route_to_multiaddr(&r)), + } + } +} + +/// Information to display of the inlets in the `ockam node show` command +#[derive(Debug, Serialize)] +pub struct ShowOutletStatus { + pub forward_address: SocketAddr, + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, +} + +impl From for ShowOutletStatus { + fn from(value: OutletStatus) -> Self { + Self { + forward_address: value.socket_addr, + address: addr_to_multiaddr(value.worker_addr), + } + } +} diff --git a/implementations/rust/ockam/ockam_command/src/node/models/secure_channel.rs b/implementations/rust/ockam/ockam_command/src/node/models/secure_channel.rs new file mode 100644 index 00000000000..f119e94d44f --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/node/models/secure_channel.rs @@ -0,0 +1,22 @@ +use ockam_api::{ + addr_to_multiaddr, nodes::models::secure_channel::ShowSecureChannelListenerResponse, +}; +use ockam_core::flow_control::FlowControlId; +use ockam_multiaddr::MultiAddr; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ShowSecureChannelListener { + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, + pub flow_control: FlowControlId, +} + +impl From for ShowSecureChannelListener { + fn from(value: ShowSecureChannelListenerResponse) -> Self { + Self { + address: addr_to_multiaddr(value.addr), + flow_control: value.flow_control_id, + } + } +} diff --git a/implementations/rust/ockam/ockam_command/src/node/models/services.rs b/implementations/rust/ockam/ockam_command/src/node/models/services.rs new file mode 100644 index 00000000000..058b21567d6 --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/node/models/services.rs @@ -0,0 +1,20 @@ +use ockam_api::{addr_to_multiaddr, nodes::models::services::ServiceStatus}; +use ockam_multiaddr::MultiAddr; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ShowServiceStatus { + #[serde(skip_serializing_if = "Option::is_none")] + pub address: Option, + #[serde(rename = "type")] + pub service_type: String, +} + +impl From for ShowServiceStatus { + fn from(value: ServiceStatus) -> Self { + Self { + address: addr_to_multiaddr(value.addr), + service_type: value.service_type, + } + } +} diff --git a/implementations/rust/ockam/ockam_command/src/node/models/show.rs b/implementations/rust/ockam/ockam_command/src/node/models/show.rs new file mode 100644 index 00000000000..fe33c10560e --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/node/models/show.rs @@ -0,0 +1,162 @@ +use std::fmt::Display; + +use colorful::Colorful; + +use ockam_multiaddr::{ + proto::{DnsAddr, Node, Tcp}, + MultiAddr, +}; +use serde::Serialize; + +use crate::output::Output; + +use super::{ + portal::{ShowInletStatus, ShowOutletStatus}, + secure_channel::ShowSecureChannelListener, + services::ShowServiceStatus, + transport::ShowTransportStatus, +}; + +#[derive(Debug, Serialize)] +pub struct ShowNodeResponse<'a> { + pub is_default: bool, + pub name: &'a str, + pub is_up: bool, + pub route: RouteToNode, + #[serde(skip_serializing_if = "Option::is_none")] + pub identity: Option, + pub transports: Vec, + pub secure_channel_listeners: Vec, + pub inlets: Vec, + pub outlets: Vec, + pub services: Vec, +} +#[derive(Debug, Serialize)] +pub struct RouteToNode { + #[serde(skip_serializing_if = "Option::is_none")] + pub short: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub verbose: Option, +} + +impl<'a> ShowNodeResponse<'a> { + pub fn new( + is_default: bool, + name: &'a str, + is_up: bool, + node_port: Option, + ) -> ShowNodeResponse { + let mut m = MultiAddr::default(); + let short = m.push_back(Node::new(name)).ok().map(|_| m); + + let verbose = node_port.and_then(|port| { + let mut m = MultiAddr::default(); + if m.push_back(DnsAddr::new("localhost")).is_ok() && m.push_back(Tcp::new(port)).is_ok() + { + Some(m) + } else { + None + } + }); + + ShowNodeResponse { + is_default, + name, + is_up, + route: RouteToNode { short, verbose }, + identity: None, + transports: Default::default(), + secure_channel_listeners: Default::default(), + inlets: Default::default(), + outlets: Default::default(), + services: Default::default(), + } + } +} + +impl<'a> Display for ShowNodeResponse<'a> { + fn fmt(&self, buffer: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(buffer, "Node:")?; + + if self.is_default { + writeln!(buffer, " Name: {} (default)", self.name)?; + } else { + writeln!(buffer, " Name: {}", self.name)?; + } + + writeln!( + buffer, + " Status: {}", + match self.is_up { + true => "UP".light_green(), + false => "DOWN".light_red(), + } + )?; + + writeln!(buffer, " Route To Node:")?; + if let Some(short) = &self.route.short { + writeln!(buffer, " Short: {short}")?; + } + if let Some(verbose) = &self.route.verbose { + writeln!(buffer, " Verbose: {verbose}")?; + } + + if let Some(identity) = &self.identity { + writeln!(buffer, " Identity: {}", identity)?; + } + + writeln!(buffer, " Transports:")?; + for e in &self.transports { + writeln!(buffer, " Transport:")?; + writeln!(buffer, " Type: {}", &e.tt)?; + writeln!(buffer, " Mode: {}", &e.mode)?; + writeln!(buffer, " Socket: {}", &e.socket)?; + writeln!(buffer, " Worker: {}", &e.worker)?; + writeln!(buffer, " FlowControlId: {}", &e.flow_control)?; + } + + writeln!(buffer, " Secure Channel Listeners:")?; + for e in &self.secure_channel_listeners { + writeln!(buffer, " Listener:")?; + if let Some(ma) = &e.address { + writeln!(buffer, " Address: {ma}")?; + } + writeln!(buffer, " FlowControlId: {}", &e.flow_control)?; + } + + writeln!(buffer, " Inlets:")?; + for e in &self.inlets { + writeln!(buffer, " Inlet:")?; + writeln!(buffer, " Listen Address: {}", e.listen_address)?; + if let Some(r) = &e.route_to_outlet { + writeln!(buffer, " Route To Outlet: {r}")?; + } + } + + writeln!(buffer, " Outlets:")?; + for e in &self.outlets { + writeln!(buffer, " Outlet:")?; + writeln!(buffer, " Forward Address: {}", e.forward_address)?; + if let Some(ma) = &e.address { + writeln!(buffer, " Address: {ma}")?; + } + } + + writeln!(buffer, " Services:")?; + for e in &self.services { + writeln!(buffer, " Service:")?; + writeln!(buffer, " Type: {}", e.service_type)?; + if let Some(ma) = &e.address { + writeln!(buffer, " Address: {ma}")?; + } + } + + Ok(()) + } +} + +impl<'a> Output for ShowNodeResponse<'a> { + fn output(&self) -> crate::error::Result { + Ok(self.to_string()) + } +} diff --git a/implementations/rust/ockam/ockam_command/src/node/models/transport.rs b/implementations/rust/ockam/ockam_command/src/node/models/transport.rs new file mode 100644 index 00000000000..d2691428298 --- /dev/null +++ b/implementations/rust/ockam/ockam_command/src/node/models/transport.rs @@ -0,0 +1,25 @@ +use ockam_api::nodes::models::transport::{TransportMode, TransportStatus, TransportType}; +use ockam_core::flow_control::FlowControlId; +use serde::Serialize; + +#[derive(Debug, Serialize)] +pub struct ShowTransportStatus { + #[serde(rename = "type")] + pub tt: TransportType, + pub mode: TransportMode, + pub socket: String, + pub worker: String, + pub flow_control: FlowControlId, +} + +impl From for ShowTransportStatus { + fn from(value: TransportStatus) -> Self { + Self { + tt: value.tt, + mode: value.tm, + socket: value.socket_addr, + worker: value.worker_addr, + flow_control: value.flow_control_id, + } + } +} diff --git a/implementations/rust/ockam/ockam_command/src/node/show.rs b/implementations/rust/ockam/ockam_command/src/node/show.rs index d890d3238c6..ecbbd858960 100644 --- a/implementations/rust/ockam/ockam_command/src/node/show.rs +++ b/implementations/rust/ockam/ockam_command/src/node/show.rs @@ -1,24 +1,26 @@ use clap::Args; -use colorful::Colorful; -use tokio_retry::strategy::FixedInterval; -use tracing::{info, trace, warn}; - -use ockam_api::cli_state::{CliState, StateDirTrait, StateItemTrait}; -use ockam_api::nodes::models::portal::{InletList, OutletList}; +use miette::IntoDiagnostic; use ockam_api::nodes::models::secure_channel::SecureChannelListenersList; use ockam_api::nodes::models::services::ServiceList; use ockam_api::nodes::models::transport::TransportList; use ockam_api::nodes::BackgroundNode; -use ockam_api::{addr_to_multiaddr, route_to_multiaddr}; -use ockam_core::Route; -use ockam_multiaddr::proto::{DnsAddr, Node, Tcp}; -use ockam_multiaddr::MultiAddr; use ockam_node::Context; +use tokio_retry::strategy::FixedInterval; +use tracing::{info, trace, warn}; + +use ockam_api::cli_state::{CliState, StateDirTrait, StateItemTrait}; +use ockam_api::nodes::models::portal::{InletList, OutletList}; use crate::node::get_node_name; use crate::node::util::check_default; use crate::util::{api, node_rpc}; -use crate::{docs, CommandGlobalOpts, OutputFormat, Result}; +use crate::{docs, CommandGlobalOpts, Result}; + +use super::models::portal::{ShowInletStatus, ShowOutletStatus}; +use super::models::secure_channel::ShowSecureChannelListener; +use super::models::services::ShowServiceStatus; +use super::models::show::ShowNodeResponse; +use super::models::transport::ShowTransportStatus; const LONG_ABOUT: &str = include_str!("./static/show/long_about.txt"); const PREVIEW_TAG: &str = include_str!("../static/preview_tag.txt"); @@ -57,120 +59,6 @@ async fn run_impl( Ok(()) } -// TODO: This function should be replaced with a better system of -// printing the node state in the future but for now we can just tell -// clippy to stop complaining about it. -#[allow(clippy::too_many_arguments)] -fn print_node_info( - opts: &CommandGlobalOpts, - node_port: Option, - node_name: &str, - is_default: bool, - status_is_up: bool, - default_id: Option<&str>, - services: Option<&ServiceList>, - tcp_listeners: Option<&TransportList>, - secure_channel_listeners: Option<&SecureChannelListenersList>, - inlets_outlets: Option<(&InletList, &OutletList)>, -) { - if opts.global_args.output_format == OutputFormat::Json { - opts.terminal - .clone() - .stdout() - .json(serde_json::json!({ "name": &node_name })) - .write_line() - .expect("Failed to write to stdout."); - return; - } - println!(); - println!("Node:"); - if is_default { - println!(" Name: {node_name} (Default)"); - } else { - println!(" Name: {node_name}"); - } - println!( - " Status: {}", - match status_is_up { - true => "UP".light_green(), - false => "DOWN".light_red(), - } - ); - - println!(" Route To Node:"); - let mut m = MultiAddr::default(); - if m.push_back(Node::new(node_name)).is_ok() { - println!(" Short: {m}"); - } - - if let Some(port) = node_port { - let mut m = MultiAddr::default(); - if m.push_back(DnsAddr::new("localhost")).is_ok() && m.push_back(Tcp::new(port)).is_ok() { - println!(" Verbose: {m}"); - } - } - - if let Some(id) = default_id { - println!(" Identity: {id}"); - } - - if let Some(list) = tcp_listeners { - println!(" Transports:"); - for e in &list.list { - println!(" Transport:"); - println!(" Type: {}", e.tt); - println!(" Mode: {}", e.tm); - println!(" Socket: {}", e.socket_addr); - println!(" Worker: {}", e.worker_addr); - println!(" FlowControlId: {}", e.flow_control_id); - } - } - - if let Some(list) = secure_channel_listeners { - println!(" Secure Channel Listeners:"); - for e in &list.list { - println!(" Listener:"); - if let Some(ma) = addr_to_multiaddr(e.addr.clone()) { - println!(" Address: {ma}"); - println!(" FlowControlId: {}", &e.flow_control_id); - } - } - } - - if let Some((inlets, outlets)) = inlets_outlets { - println!(" Inlets:"); - for e in &inlets.list { - println!(" Inlet:"); - println!(" Listen Address: {}", e.bind_addr); - if let Some(r) = Route::parse(&e.outlet_route) { - if let Some(ma) = route_to_multiaddr(&r) { - println!(" Route To Outlet: {ma}"); - } - } - } - println!(" Outlets:"); - for e in &outlets.list { - println!(" Outlet:"); - println!(" Forward Address: {}", e.socket_addr); - - if let Some(ma) = addr_to_multiaddr(e.worker_addr.to_string()) { - println!(" Address: {ma}"); - } - } - } - - if let Some(list) = services { - println!(" Services:"); - for e in &list.list { - println!(" Service:"); - println!(" Type: {}", e.service_type); - if let Some(ma) = addr_to_multiaddr(e.addr.as_str()) { - println!(" Address: {ma}"); - } - } - } -} - pub async fn print_query_status( opts: &CommandGlobalOpts, ctx: &Context, @@ -180,75 +68,85 @@ pub async fn print_query_status( is_default: bool, ) -> miette::Result<()> { let cli_state = opts.state.clone(); - if !is_node_up(ctx, node_name, node, cli_state.clone(), wait_until_ready).await? { - let node_state = cli_state.nodes.get(node_name)?; - let node_port = node_state - .config() - .setup() - .api_transport() - .ok() - .map(|listener| listener.addr.port()); - // it is expected to not be able to open an arbitrary TCP connection on an authority node - // so in that case we display an UP status - let is_authority_node = node_state.config().setup().authority_node.unwrap_or(false); - print_node_info( - opts, - node_port, - node_name, - is_default, - is_authority_node, - None, - None, - None, - None, - None, - ); - } else { - let node_state = cli_state.nodes.get(node_name)?; - // Get short id for the node - let default_id = match node_state.config().identity_config() { - Ok(resp) => resp.identifier().to_string(), - Err(_) => String::from("None"), + let node_info = + if !is_node_up(ctx, node_name, node, cli_state.clone(), wait_until_ready).await? { + let node_state = cli_state.nodes.get(node_name)?; + let node_port = node_state + .config() + .setup() + .api_transport() + .ok() + .map(|listener| listener.addr.port()); + + // it is expected to not be able to open an arbitrary TCP connection on an authority node + // so in that case we display an UP status + let is_authority_node = node_state.config().setup().authority_node.unwrap_or(false); + + ShowNodeResponse::new(is_default, node_name, is_authority_node, node_port) + } else { + let node_state = cli_state.nodes.get(node_name)?; + let node_port = node_state + .config() + .setup() + .api_transport() + .ok() + .map(|listener| listener.addr.port()); + + let mut node_info = ShowNodeResponse::new(is_default, node_name, true, node_port); + + // Get short id for the node + node_info.identity = Some(match node_state.config().identity_config() { + Ok(resp) => resp.identifier().to_string(), + Err(_) => String::from("None"), + }); + + // Get list of services for the node + let services: ServiceList = node.ask(ctx, api::list_services()).await?; + node_info.services = services + .list + .into_iter() + .map(ShowServiceStatus::from) + .collect(); + + // Get list of TCP listeners for node + let transports: TransportList = node.ask(ctx, api::list_tcp_listeners()).await?; + node_info.transports = transports + .list + .into_iter() + .map(ShowTransportStatus::from) + .collect(); + + // Get list of Secure Channel Listeners + let listeners: SecureChannelListenersList = + node.ask(ctx, api::list_secure_channel_listener()).await?; + node_info.secure_channel_listeners = listeners + .list + .into_iter() + .map(ShowSecureChannelListener::from) + .collect(); + + // Get list of inlets + let inlets: InletList = node.ask(ctx, api::list_inlets()).await?; + node_info.inlets = inlets.list.into_iter().map(ShowInletStatus::from).collect(); + + // Get list of outlets + let outlets: OutletList = node.ask(ctx, api::list_outlets()).await?; + node_info.outlets = outlets + .list + .into_iter() + .map(ShowOutletStatus::from) + .collect(); + + node_info }; - // Get list of services for the node - let services: ServiceList = node.ask(ctx, api::list_services()).await?; - - // Get list of TCP listeners for node - let tcp_listeners: TransportList = node.ask(ctx, api::list_tcp_listeners()).await?; - - // Get list of Secure Channel Listeners - let secure_channel_listeners: SecureChannelListenersList = - node.ask(ctx, api::list_secure_channel_listener()).await?; - - // Get list of inlets - let inlets: InletList = node.ask(ctx, api::list_inlets()).await?; - - // Get list of outlets - let outlets: OutletList = node.ask(ctx, api::list_outlets()).await?; - - let node_state = cli_state.nodes.get(node_name)?; - let node_port = node_state - .config() - .setup() - .api_transport() - .ok() - .map(|listener| listener.addr.port()); - - print_node_info( - opts, - node_port, - node_name, - is_default, - true, - Some(&default_id), - Some(&services), - Some(&tcp_listeners), - Some(&secure_channel_listeners), - Some((&inlets, &outlets)), - ); - } + opts.terminal + .clone() + .stdout() + .plain(&node_info) + .json(serde_json::to_string_pretty(&node_info).into_diagnostic()?) + .write_line()?; Ok(()) }