diff --git a/easytier/src/easytier-cli.rs b/easytier/src/easytier-cli.rs index 3fac958..7e3fedf 100644 --- a/easytier/src/easytier-cli.rs +++ b/easytier/src/easytier-cli.rs @@ -109,6 +109,8 @@ enum SubCommand { Stats(StatsArgs), #[command(about = "manage logger configuration")] Logger(LoggerArgs), + #[command(about = "manage network instance configuration")] + Config(ConfigArgs), #[command(about = t!("core_clap.generate_completions").to_string())] GenAutocomplete { shell: Shell }, } @@ -293,6 +295,23 @@ enum LoggerSubCommand { }, } +#[derive(Args, Debug)] +struct ConfigArgs { + #[command(subcommand)] + sub_command: Option, +} + +#[derive(Subcommand, Debug)] +enum ConfigSubCommand { + /// List network instances and their configurations + List, + /// Get configuration for a specific instance + Get { + #[arg(help = "Instance ID")] + inst_id: String, + }, +} + #[derive(Args, Debug)] struct ServiceArgs { #[arg(short, long, default_value = env!("CARGO_PKG_NAME"), help = "service name")] @@ -1286,6 +1305,68 @@ impl CommandHandler<'_> { } Ok(ports) } + + async fn handle_config_list(&self) -> Result<(), Error> { + let client = self.get_peer_manager_client().await?; + let node_info = client + .show_node_info(BaseController::default(), ShowNodeInfoRequest::default()) + .await? + .node_info + .ok_or(anyhow::anyhow!("node info not found"))?; + + if self.verbose || *self.output_format == OutputFormat::Json { + println!("{}", serde_json::to_string_pretty(&node_info)?); + return Ok(()); + } + + #[derive(tabled::Tabled, serde::Serialize)] + struct ConfigTableItem { + #[tabled(rename = "Instance ID")] + inst_id: String, + #[tabled(rename = "Virtual IP")] + ipv4: String, + #[tabled(rename = "Hostname")] + hostname: String, + #[tabled(rename = "Network Name")] + network_name: String, + } + + let items = vec![ConfigTableItem { + inst_id: node_info.peer_id.to_string(), + ipv4: node_info.ipv4_addr, + hostname: node_info.hostname, + network_name: node_info.network_name, + }]; + + print_output(&items, self.output_format)?; + Ok(()) + } + + async fn handle_config_get(&self, inst_id: &str) -> Result<(), Error> { + let client = self.get_peer_manager_client().await?; + let node_info = client + .show_node_info(BaseController::default(), ShowNodeInfoRequest::default()) + .await? + .node_info + .ok_or(anyhow::anyhow!("node info not found"))?; + + // Check if the requested instance ID matches the current node + if node_info.peer_id.to_string() != inst_id { + return Err(anyhow::anyhow!( + "Instance ID {} not found. Current instance ID is {}", + inst_id, + node_info.peer_id + )); + } + + if self.verbose || *self.output_format == OutputFormat::Json { + println!("{}", serde_json::to_string_pretty(&node_info)?); + return Ok(()); + } + + println!("{}", node_info.config); + Ok(()) + } } #[derive(Debug)] @@ -2097,6 +2178,14 @@ async fn main() -> Result<(), Error> { handler.handle_logger_set(level).await?; } }, + SubCommand::Config(config_args) => match &config_args.sub_command { + Some(ConfigSubCommand::List) | None => { + handler.handle_config_list().await?; + } + Some(ConfigSubCommand::Get { inst_id }) => { + handler.handle_config_get(inst_id).await?; + } + }, SubCommand::GenAutocomplete { shell } => { let mut cmd = Cli::command(); easytier::print_completions(shell, &mut cmd, "easytier-cli"); diff --git a/easytier/src/instance_manager.rs b/easytier/src/instance_manager.rs index dca8013..4ddd722 100644 --- a/easytier/src/instance_manager.rs +++ b/easytier/src/instance_manager.rs @@ -140,6 +140,12 @@ impl NetworkInstanceManager { .and_then(|instance| instance.value().get_running_info()) } + pub fn get_network_config(&self, instance_id: &uuid::Uuid) -> Option { + self.instance_map + .get(instance_id) + .map(|instance| instance.value().get_config()) + } + pub fn list_network_instance_ids(&self) -> Vec { self.instance_map.iter().map(|item| *item.key()).collect() } diff --git a/easytier/src/launcher.rs b/easytier/src/launcher.rs index 5304286..84e7dc5 100644 --- a/easytier/src/launcher.rs +++ b/easytier/src/launcher.rs @@ -460,6 +460,10 @@ impl NetworkInstance { None } } + + pub fn get_config(&self) -> TomlConfigLoader { + self.config.clone() + } } pub fn add_proxy_network_to_config( diff --git a/easytier/src/proto/web.proto b/easytier/src/proto/web.proto index 9b73856..9d409e1 100644 --- a/easytier/src/proto/web.proto +++ b/easytier/src/proto/web.proto @@ -178,6 +178,15 @@ message DeleteNetworkInstanceResponse { repeated common.UUID remain_inst_ids = 1; } +message GetConfigRequest { + common.UUID inst_id = 1; +} + +message GetConfigResponse { + NetworkConfig config = 1; + string toml_config = 2; +} + service WebClientService { rpc ValidateConfig(ValidateConfigRequest) returns (ValidateConfigResponse) {} rpc RunNetworkInstance(RunNetworkInstanceRequest) returns (RunNetworkInstanceResponse) {} @@ -185,4 +194,5 @@ service WebClientService { rpc CollectNetworkInfo(CollectNetworkInfoRequest) returns (CollectNetworkInfoResponse) {} rpc ListNetworkInstance(ListNetworkInstanceRequest) returns (ListNetworkInstanceResponse) {} rpc DeleteNetworkInstance(DeleteNetworkInstanceRequest) returns (DeleteNetworkInstanceResponse) {} + rpc GetConfig(GetConfigRequest) returns (GetConfigResponse) {} } diff --git a/easytier/src/web_client/controller.rs b/easytier/src/web_client/controller.rs index 1133caa..c22517c 100644 --- a/easytier/src/web_client/controller.rs +++ b/easytier/src/web_client/controller.rs @@ -6,10 +6,11 @@ use crate::{ rpc_types::{self, controller::BaseController}, web::{ CollectNetworkInfoRequest, CollectNetworkInfoResponse, DeleteNetworkInstanceRequest, - DeleteNetworkInstanceResponse, ListNetworkInstanceRequest, ListNetworkInstanceResponse, - NetworkInstanceRunningInfoMap, RetainNetworkInstanceRequest, - RetainNetworkInstanceResponse, RunNetworkInstanceRequest, RunNetworkInstanceResponse, - ValidateConfigRequest, ValidateConfigResponse, WebClientService, + DeleteNetworkInstanceResponse, GetConfigRequest, GetConfigResponse, + ListNetworkInstanceRequest, ListNetworkInstanceResponse, NetworkInstanceRunningInfoMap, + RetainNetworkInstanceRequest, RetainNetworkInstanceResponse, RunNetworkInstanceRequest, + RunNetworkInstanceResponse, ValidateConfigRequest, ValidateConfigResponse, + WebClientService, }, }, }; @@ -153,4 +154,37 @@ impl WebClientService for Controller { remain_inst_ids: remain_inst_ids.into_iter().map(Into::into).collect(), }) } + + // rpc GetConfig(GetConfigRequest) returns (GetConfigResponse) {} + async fn get_config( + &self, + _: BaseController, + req: GetConfigRequest, + ) -> Result { + let inst_id = req.inst_id.ok_or_else(|| { + rpc_types::error::Error::ExecutionError( + anyhow::anyhow!("instance_id is required").into(), + ) + })?; + + let config = self + .manager + .get_network_config(&inst_id.into()) + .ok_or_else(|| { + rpc_types::error::Error::ExecutionError( + anyhow::anyhow!("instance {} not found", inst_id).into(), + ) + })?; + + // Get the NetworkConfig from the instance + let network_config = crate::launcher::NetworkConfig::new_from_config(&config)?; + + // Get the TOML config string + let toml_config = config.dump(); + + Ok(GetConfigResponse { + config: Some(network_config), + toml_config, + }) + } }