diff --git a/implementations/rust/ockam/ockam_api/src/nodes/service/background_node.rs b/implementations/rust/ockam/ockam_api/src/nodes/service/background_node.rs index 0cdcaacccb8..8529ab74593 100644 --- a/implementations/rust/ockam/ockam_api/src/nodes/service/background_node.rs +++ b/implementations/rust/ockam/ockam_api/src/nodes/service/background_node.rs @@ -183,4 +183,9 @@ impl BackgroundNode { let route = self.create_route().await?; Ok(Client::new(&route, timeout)) } + + /// Get the node name + pub fn name(&self) -> &str { + &self.node_name + } } diff --git a/implementations/rust/ockam/ockam_command/src/node/delete.rs b/implementations/rust/ockam/ockam_command/src/node/delete.rs index eceb2e909e1..f2214e06837 100644 --- a/implementations/rust/ockam/ockam_command/src/node/delete.rs +++ b/implementations/rust/ockam/ockam_command/src/node/delete.rs @@ -46,15 +46,15 @@ async fn run_impl( _ctx: Context, (opts, cmd): (CommandGlobalOpts, DeleteCommand), ) -> miette::Result<()> { - NodeDeleteTui::run(opts, cmd).await + DeleteTui::run(opts, cmd).await } -pub struct NodeDeleteTui { +pub struct DeleteTui { opts: CommandGlobalOpts, cmd: DeleteCommand, } -impl NodeDeleteTui { +impl DeleteTui { pub async fn run(opts: CommandGlobalOpts, cmd: DeleteCommand) -> miette::Result<()> { let tui = Self { opts, cmd }; tui.delete().await @@ -62,8 +62,8 @@ impl NodeDeleteTui { } #[ockam_core::async_trait] -impl DeleteCommandTui for NodeDeleteTui { - const ITEM_NAME: &'static str = "nodes"; +impl DeleteCommandTui for DeleteTui { + const ITEM_NAME: &'static str = "node"; fn cmd_arg_item_name(&self) -> Option<&str> { self.cmd.node_name.as_deref() @@ -81,39 +81,45 @@ impl DeleteCommandTui for NodeDeleteTui { self.opts.terminal.clone() } - fn list_items_names(&self) -> miette::Result> { + async fn get_arg_item_name_or_default(&self) -> miette::Result { + Ok(get_node_name(&self.opts.state, &self.cmd.node_name)) + } + + async fn list_items_names(&self) -> miette::Result> { Ok(self.opts.state.nodes.list_items_names()?) } - async fn delete_single(&self) -> miette::Result<()> { - let node_name = get_node_name(&self.opts.state, &self.cmd.node_name); + async fn delete_single(&self, item_name: &str) -> miette::Result<()> { self.opts .state .nodes - .delete_sigkill(&node_name, self.cmd.force)?; + .delete_sigkill(item_name, self.cmd.force)?; self.terminal() .stdout() - .plain(fmt_ok!("Node with name '{node_name}' has been deleted")) - .machine(&node_name) - .json(serde_json::json!({ "name": &node_name })) + .plain(fmt_ok!( + "Node with name {} has been deleted", + item_name.light_magenta() + )) + .machine(item_name) + .json(serde_json::json!({ "name": &item_name })) .write_line()?; Ok(()) } - async fn delete_multiple(&self, selected_items_names: Vec) -> miette::Result<()> { - let plain = selected_items_names - .iter() + async fn delete_multiple(&self, items_names: Vec) -> miette::Result<()> { + let plain = items_names + .into_iter() .map(|name| { if self .opts .state .nodes - .delete_sigkill(name, self.cmd.force) + .delete_sigkill(&name, self.cmd.force) .is_ok() { - fmt_ok!("Node '{name}' deleted\n") + fmt_ok!("Node {} deleted\n", name.light_magenta()) } else { - fmt_warn!("Failed to delete node '{name}'\n") + fmt_warn!("Failed to delete node {}\n", name.light_magenta()) } }) .collect::(); diff --git a/implementations/rust/ockam/ockam_command/src/node/show.rs b/implementations/rust/ockam/ockam_command/src/node/show.rs index 95ccd85e307..b8c350a121f 100644 --- a/implementations/rust/ockam/ockam_command/src/node/show.rs +++ b/implementations/rust/ockam/ockam_command/src/node/show.rs @@ -53,16 +53,16 @@ async fn run_impl( ctx: Context, (opts, cmd): (CommandGlobalOpts, ShowCommand), ) -> miette::Result<()> { - NodeShowTui::run(ctx, opts, cmd.node_name).await + ShowTui::run(ctx, opts, cmd.node_name).await } -pub struct NodeShowTui { +pub struct ShowTui { ctx: Context, opts: CommandGlobalOpts, node_name: Option, } -impl NodeShowTui { +impl ShowTui { pub async fn run( ctx: Context, opts: CommandGlobalOpts, @@ -78,7 +78,7 @@ impl NodeShowTui { } #[ockam_core::async_trait] -impl ShowCommandTui for NodeShowTui { +impl ShowCommandTui for ShowTui { const ITEM_NAME: &'static str = "nodes"; fn cmd_arg_item_name(&self) -> Option<&str> { @@ -89,14 +89,17 @@ impl ShowCommandTui for NodeShowTui { self.opts.terminal.clone() } + async fn get_arg_item_name_or_default(&self) -> miette::Result { + Ok(get_node_name(&self.opts.state, &self.node_name)) + } + async fn list_items_names(&self) -> miette::Result> { Ok(self.opts.state.nodes.list_items_names()?) } - async fn show_single(&self) -> miette::Result<()> { - let node_name = get_node_name(&self.opts.state, &self.node_name); - let mut node = BackgroundNode::create(&self.ctx, &self.opts.state, &node_name).await?; - print_query_status(&self.opts, &self.ctx, &node_name, &mut node, false).await?; + async fn show_single(&self, item_name: &str) -> miette::Result<()> { + let mut node = BackgroundNode::create(&self.ctx, &self.opts.state, item_name).await?; + print_query_status(&self.opts, &self.ctx, item_name, &mut node, false).await?; Ok(()) } diff --git a/implementations/rust/ockam/ockam_command/src/tcp/inlet/delete.rs b/implementations/rust/ockam/ockam_command/src/tcp/inlet/delete.rs index 8c9715c1b74..fb23fa31cce 100644 --- a/implementations/rust/ockam/ockam_command/src/tcp/inlet/delete.rs +++ b/implementations/rust/ockam/ockam_command/src/tcp/inlet/delete.rs @@ -1,16 +1,20 @@ use clap::Args; use colorful::Colorful; -use miette::{miette, IntoDiagnostic}; +use console::Term; +use miette::miette; use ockam::Context; +use ockam_api::nodes::models::portal::InletList; use ockam_api::nodes::service::portals::Inlets; use ockam_api::nodes::BackgroundNode; +use ockam_core::api::Request; use crate::fmt_ok; use crate::node::{get_node_name, initialize_node_if_default, NodeOpts}; use crate::tcp::util::alias_parser; +use crate::terminal::tui::DeleteCommandTui; use crate::util::{node_rpc, parse_node_name}; -use crate::{docs, CommandGlobalOpts}; +use crate::{docs, fmt_warn, CommandGlobalOpts, Terminal, TerminalStream}; const AFTER_LONG_HELP: &str = include_str!("./static/delete/after_long_help.txt"); @@ -19,8 +23,8 @@ const AFTER_LONG_HELP: &str = include_str!("./static/delete/after_long_help.txt" #[command(after_long_help = docs::after_help(AFTER_LONG_HELP))] pub struct DeleteCommand { /// Delete the inlet with this alias - #[arg(display_order = 900, required = true, id = "ALIAS", value_parser = alias_parser)] - alias: String, + #[arg(display_order = 900, id = "ALIAS", value_parser = alias_parser)] + alias: Option, /// Node on which to stop the tcp inlet. If none are provided, the default node will be used #[command(flatten)] @@ -42,31 +46,103 @@ pub async fn run_impl( ctx: Context, (opts, cmd): (CommandGlobalOpts, DeleteCommand), ) -> miette::Result<()> { - let node_name = get_node_name(&opts.state, &cmd.node_opts.at_node); - let node_name = parse_node_name(&node_name)?; - let node = BackgroundNode::create(&ctx, &opts.state, &node_name).await?; - if opts - .terminal - .confirmed_with_flag_or_prompt(cmd.yes, "Are you sure you want to delete this TCP inlet?")? - { - let alias = cmd.alias; - node.delete_inlet(&ctx, &alias) - .await? - .found() - .into_diagnostic()? - .ok_or(miette!( - "TCP inlet with alias {alias} was not found on Node {node_name}" - ))?; - - opts.terminal + DeleteTui::run(ctx, opts, cmd).await +} + +struct DeleteTui { + ctx: Context, + opts: CommandGlobalOpts, + node: BackgroundNode, + cmd: DeleteCommand, +} + +impl DeleteTui { + pub async fn run( + ctx: Context, + opts: CommandGlobalOpts, + cmd: DeleteCommand, + ) -> miette::Result<()> { + let node_name = { + let name = get_node_name(&opts.state, &cmd.node_opts.at_node); + parse_node_name(&name)? + }; + let node = BackgroundNode::create(&ctx, &opts.state, &node_name).await?; + let tui = Self { + ctx, + opts, + node, + cmd, + }; + tui.delete().await + } +} + +#[ockam_core::async_trait] +impl DeleteCommandTui for DeleteTui { + const ITEM_NAME: &'static str = "inlet"; + + fn cmd_arg_item_name(&self) -> Option<&str> { + self.cmd.alias.as_deref() + } + + fn cmd_arg_delete_all(&self) -> bool { + false + } + + fn cmd_arg_confirm_deletion(&self) -> bool { + self.cmd.yes + } + + fn terminal(&self) -> Terminal> { + self.opts.terminal.clone() + } + + async fn get_arg_item_name_or_default(&self) -> miette::Result { + self.cmd.alias.clone().ok_or(miette!("No alias provided")) + } + + async fn list_items_names(&self) -> miette::Result> { + let inlets: InletList = self + .node + .ask(&self.ctx, Request::get("/node/inlet")) + .await?; + let names = inlets.list.into_iter().map(|i| i.alias).collect(); + Ok(names) + } + + async fn delete_single(&self, item_name: &str) -> miette::Result<()> { + let node_name = self.node.name(); + self.node.delete_inlet(&self.ctx, item_name).await?; + self.terminal() .stdout() .plain(fmt_ok!( - "TCP inlet with alias {alias} on Node {node_name} has been deleted" + "TCP inlet with alias {} on Node {} has been deleted", + item_name.light_magenta(), + node_name.light_magenta() )) - .machine(&alias) - .json(serde_json::json!({ "alias": alias, "node": node_name })) - .write_line() - .unwrap(); + .write_line()?; + Ok(()) + } + + async fn delete_multiple(&self, items_names: Vec) -> miette::Result<()> { + let node_name = self.node.name(); + let mut plain = String::new(); + for item_name in items_names { + if self.node.delete_inlet(&self.ctx, &item_name).await.is_ok() { + plain.push_str(&fmt_ok!( + "TCP inlet with alias {} on Node {} has been deleted\n", + item_name.light_magenta(), + node_name.light_magenta() + )); + } else { + plain.push_str(&fmt_warn!( + "Failed to delete TCP inlet with alias {} on Node {}\n", + item_name.light_magenta(), + node_name.light_magenta() + )); + } + } + self.terminal().stdout().plain(plain).write_line()?; + Ok(()) } - Ok(()) } diff --git a/implementations/rust/ockam/ockam_command/src/terminal/tui.rs b/implementations/rust/ockam/ockam_command/src/terminal/tui.rs index 5dd555be9bc..dfafb7d11e9 100644 --- a/implementations/rust/ockam/ockam_command/src/terminal/tui.rs +++ b/implementations/rust/ockam/ockam_command/src/terminal/tui.rs @@ -1,6 +1,7 @@ use crate::{fmt_info, Terminal, TerminalStream}; use colorful::Colorful; use console::Term; +use miette::miette; #[ockam_core::async_trait] pub trait ShowCommandTui { @@ -9,8 +10,9 @@ pub trait ShowCommandTui { fn cmd_arg_item_name(&self) -> Option<&str>; fn terminal(&self) -> Terminal>; + async fn get_arg_item_name_or_default(&self) -> miette::Result; async fn list_items_names(&self) -> miette::Result>; - async fn show_single(&self) -> miette::Result<()>; + async fn show_single(&self, item_name: &str) -> miette::Result<()>; async fn show_multiple(&self, items_names: Vec) -> miette::Result<()>; async fn show(&self) -> miette::Result<()> { @@ -25,7 +27,15 @@ pub trait ShowCommandTui { } if self.cmd_arg_item_name().is_some() || !terminal.can_ask_for_user_input() { - self.show_single().await?; + let item_name = self.get_arg_item_name_or_default().await?; + if !items_names.contains(&item_name) { + return Err(miette!( + "The {} {} was not found", + Self::ITEM_NAME, + item_name.light_magenta() + )); + } + self.show_single(&item_name).await?; return Ok(()); } @@ -34,7 +44,8 @@ pub trait ShowCommandTui { unreachable!("this case is already handled above"); } 1 => { - self.show_single().await?; + let item_name = items_names[0].as_str(); + self.show_single(item_name).await?; } _ => { let selected_item_names = terminal.select_multiple( @@ -48,11 +59,12 @@ pub trait ShowCommandTui { 0 => { terminal .stdout() - .plain(format!("No {} selected to show", Self::ITEM_NAME)) + .plain(fmt_info!("No {} selected to show", Self::ITEM_NAME)) .write_line()?; } 1 => { - self.show_single().await?; + let item_name = selected_item_names[0].as_str(); + self.show_single(item_name).await?; } _ => { self.show_multiple(selected_item_names).await?; @@ -73,17 +85,18 @@ pub trait DeleteCommandTui { fn cmd_arg_confirm_deletion(&self) -> bool; fn terminal(&self) -> Terminal>; - fn list_items_names(&self) -> miette::Result>; - async fn delete_single(&self) -> miette::Result<()>; + async fn get_arg_item_name_or_default(&self) -> miette::Result; + async fn list_items_names(&self) -> miette::Result>; + async fn delete_single(&self, item_name: &str) -> miette::Result<()>; async fn delete_multiple(&self, items_names: Vec) -> miette::Result<()>; async fn delete(&self) -> miette::Result<()> { let terminal = self.terminal(); - let items_names = self.list_items_names()?; + let items_names = self.list_items_names().await?; if items_names.is_empty() { terminal .stdout() - .plain(fmt_info!("There are no {} to delete", Self::ITEM_NAME)) + .plain(fmt_info!("There are no {}s to delete", Self::ITEM_NAME)) .write_line()?; return Ok(()); } @@ -99,7 +112,20 @@ pub trait DeleteCommandTui { } if self.cmd_arg_item_name().is_some() || !terminal.can_ask_for_user_input() { - self.delete_single().await?; + let item_name = self.get_arg_item_name_or_default().await?; + if !items_names.contains(&item_name) { + return Err(miette!( + "The {} {} was not found", + Self::ITEM_NAME, + item_name.light_magenta() + )); + } + if terminal.confirmed_with_flag_or_prompt( + self.cmd_arg_confirm_deletion(), + "Are you sure you want to proceed?", + )? { + self.delete_single(&item_name).await?; + } return Ok(()); } @@ -112,13 +138,14 @@ pub trait DeleteCommandTui { self.cmd_arg_confirm_deletion(), "Are you sure you want to proceed?", )? { - self.delete_single().await?; + let item_name = items_names[0].as_str(); + self.delete_single(item_name).await?; } } _ => { let selected_item_names = terminal.select_multiple( format!( - "Select one or more {} that you want to delete", + "Select one or more {}s that you want to delete", Self::ITEM_NAME ), items_names, @@ -127,7 +154,7 @@ pub trait DeleteCommandTui { 0 => { terminal .stdout() - .plain(format!("No {} selected to delete", Self::ITEM_NAME)) + .plain(fmt_info!("No {}s selected to delete", Self::ITEM_NAME)) .write_line()?; } 1 => { @@ -135,7 +162,8 @@ pub trait DeleteCommandTui { self.cmd_arg_confirm_deletion(), "Are you sure you want to proceed?", )? { - self.delete_single().await?; + let item_name = selected_item_names[0].as_str(); + self.delete_single(item_name).await?; } } _ => {