From 2b18893bb4ce5812971b9fcefe6935c79a4c4ced Mon Sep 17 00:00:00 2001 From: kevinjaypatel Date: Fri, 18 Oct 2024 12:41:01 -0700 Subject: [PATCH] =?UTF-8?q?feat(cli-wrapper):=20added=20Node=20CLI=20wrapp?= =?UTF-8?q?er=20with=20initial=20commands=20and=20config=20parsing=20=09?= =?UTF-8?q?=E2=80=A2=09Implemented=20wrapper=20with=20commands=20to=20star?= =?UTF-8?q?t=20coordinator=20and=20nodes.=20=09=E2=80=A2=09Added=20default?= =?UTF-8?q?=20node=20configuration=20file=20and=20logic=20to=20create=20no?= =?UTF-8?q?de=20home=20directory=20if=20missing.=20=09=E2=80=A2=09Introduc?= =?UTF-8?q?ed=20child=20process=20handling=20with=20temporary=20resolution?= =?UTF-8?q?=20for=20termination=20issues.=20=09=E2=80=A2=09Defined=20node?= =?UTF-8?q?=20configuration=20struct=20with=20methods=20for=20reading=20an?= =?UTF-8?q?d=20parsing=20config=20data.=20=09=E2=80=A2=09Updated=20README.?= =?UTF-8?q?md=20with=20usage=20instructions.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 33 ++++- Cargo.toml | 1 + crates/merow/Cargo.toml | 19 +++ crates/merow/README.md | 90 +++++++++++++ crates/merow/config/default.toml | 11 ++ crates/merow/src/cli.rs | 209 +++++++++++++++++++++++++++++++ crates/merow/src/main.rs | 13 ++ 7 files changed, 370 insertions(+), 6 deletions(-) create mode 100644 crates/merow/Cargo.toml create mode 100644 crates/merow/README.md create mode 100644 crates/merow/config/default.toml create mode 100644 crates/merow/src/cli.rs create mode 100644 crates/merow/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 0c0d90b50..0701c7790 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,9 +172,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" [[package]] name = "arbitrary" @@ -850,7 +850,7 @@ dependencies = [ "eyre", "libp2p", "serde", - "toml", + "toml 0.8.19", ] [[package]] @@ -4489,6 +4489,18 @@ dependencies = [ "url", ] +[[package]] +name = "merow" +version = "0.1.0" +dependencies = [ + "clap", + "const_format", + "eyre", + "serde", + "tokio", + "toml 0.5.11", +] + [[package]] name = "mime" version = "0.3.17" @@ -7807,6 +7819,15 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "toml" version = "0.8.19" @@ -8062,7 +8083,7 @@ dependencies = [ "serde_derive", "serde_json", "termcolor", - "toml", + "toml 0.8.19", ] [[package]] @@ -8509,7 +8530,7 @@ dependencies = [ "serde_json", "serde_yaml", "thiserror", - "toml", + "toml 0.8.19", "url", ] @@ -8704,7 +8725,7 @@ dependencies = [ "tar", "tempfile", "thiserror", - "toml", + "toml 0.8.19", "url", "wasmer-config", ] diff --git a/Cargo.toml b/Cargo.toml index aec88a5c6..998adba2d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ members = [ "./crates/context/config", "./crates/meroctl", "./crates/merod", + "./crates/merow", "./crates/network", "./crates/node", "./crates/node-primitives", diff --git a/crates/merow/Cargo.toml b/crates/merow/Cargo.toml new file mode 100644 index 000000000..4167ff89e --- /dev/null +++ b/crates/merow/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "merow" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true + +[dependencies] +clap = { workspace = true, features = ["env", "derive"] } +const_format.workspace = true +eyre.workspace = true + +tokio = { workspace = true, features = ["full"] } +toml = "0.5.2" +serde = { workspace = true, features = ["derive"] } + +[lints] +workspace = true diff --git a/crates/merow/README.md b/crates/merow/README.md new file mode 100644 index 000000000..af1fb4513 --- /dev/null +++ b/crates/merow/README.md @@ -0,0 +1,90 @@ +# Core Cli Wrapper (crate::merow) + +A CLI wrapper for the Calimero Node that provides a default node configuration file for initializing a node, and quickly running a development environment for testing P2P Calimero Apps. + +## Features + +- Custom Node Configuration File +- Simple Commands to Initialize and Run a Calimero Node +- Creates a Node Home Directory (if it doesn't already exist) + +## Prerequisites +- Rust: [Official Rust Installation](https://www.rust-lang.org/tools/install) + +## Setting up +Clone the project + +```bash + git clone https://github.com/kevinjaypatel/core.git +``` + +Change to repo + +```bash + cd core +``` + +Check out cli-wrapper +```bash + git branch cli-wrapper +``` + +## Usage + +Setup the Default Configuration: `./crates/merow/config/default.toml` + +```javascript +[coordinator] +name = "coordinator" +server_port = 2427 +swarm_port = 2527 +home = "data" + +[admin] +name = "node1" +server_port = 2428 +swarm_port = 2528 +home = "data" +``` + +Initialize a coordinator +`$ merow -- init-coordinator` + +Initialize a node +`$ merow -- init-node` + +Start a running coordinator +`$ merow -- start-coordinator` + +Start a running node +`$ merow -- start-node` + + +## How to Run (from project root) + +### Build the Rust Package +```bash + cargo build +``` + +### Starting up a Coordinator (same steps apply for Node Configuration) +E.g. Initializes Coordinator (with defaults) +```bash + cargo run -p merow -- init-coordinator +``` + +Start a running coordinator +```bash + cargo run -p merow -- start-coordinator +``` + +### Accessing the coordinator via Admin Dashboard +```bash + http://localhost:/admin-dashboard/ +``` + +## Roadmap + +- Additional commands for `Dev Context` creation and `Peer Invitation` +- Add a boolean flag to the Configuration File for Deploying the Admin Dashboard +- Multi-node deployment (e.g. node1, node2, ... nodeN) diff --git a/crates/merow/config/default.toml b/crates/merow/config/default.toml new file mode 100644 index 000000000..3d642be33 --- /dev/null +++ b/crates/merow/config/default.toml @@ -0,0 +1,11 @@ +[coordinator] +name = "coordinator" +server_port = 2427 +swarm_port = 2527 +home = "data" + +[admin] +name = "node1" +server_port = 2428 +swarm_port = 2528 +home = "data" \ No newline at end of file diff --git a/crates/merow/src/cli.rs b/crates/merow/src/cli.rs new file mode 100644 index 000000000..2be60b16e --- /dev/null +++ b/crates/merow/src/cli.rs @@ -0,0 +1,209 @@ +use clap::Parser; +use const_format::concatcp; +use eyre::Result as EyreResult; +use serde::{Deserialize, Serialize}; +use std::fs; +use std::path::Path; +use std::process::exit; +use std::process::{Command, Output, Stdio}; +use toml; + +pub const EXAMPLES: &str = r" + + # Initialize a coordinator + $ merow -- init-coordinator + + # Initialize a node + $ merow -- init-node + + # Start a running coordinator + $ merow -- start-coordinator + + # Start a running node + $ merow -- start-node +"; + +// Points to the Node Cofiguration Filepath relative to the working directory +const CONFIG_FILE_PATH: &str = "crates/merow/config/default.toml"; + +#[derive(Debug, Parser)] +#[command(author, version, about, long_about = None)] +#[command(after_help = concatcp!( + "Examples:", + EXAMPLES +))] +pub struct RootCommand { + /// Name of the command + pub action: String, +} + +#[derive(Serialize, Deserialize, Debug)] +struct NodeData { + coordinator: NodeConfig, + admin: NodeConfig, +} + +#[derive(Serialize, Deserialize, Debug)] +struct NodeConfig { + name: String, + server_port: u16, + swarm_port: u16, + home: String, +} + +fn build_command( + name: &str, + home: &str, + server: Option<&str>, + swarm: Option<&str>, + run_node: bool, +) -> Command { + let mut command: Command = Command::new("cargo"); + + // Sets the default CLI arguments + command.args([ + "run", + "-p", + "merod", + "--", + "--node-name", + name, + "--home", + home, + ]); + + // Sets the custom CLI arguments + if !run_node { + command.args([ + "init", + "--server-port", + server.unwrap(), + "--swarm-port", + swarm.unwrap(), + ]); + } else { + command.arg("run"); + } + + command.stdout(Stdio::piped()); // Capture stdout + command.stderr(Stdio::piped()); // Capture stderr + + return command; +} + +fn display_command_output(output: Output) { + println!("Status: {}", output.status); + println!("Stdout: {}", String::from_utf8_lossy(&output.stdout)); + println!("Stderr: {}", String::from_utf8_lossy(&output.stderr)); +} + +fn make_direcory(node_home: &str) { + match fs::create_dir(node_home) { + Ok(()) => println!("Created Home Directory: ./{}\n", node_home), + Err(error) => panic!("Problem creating the Node Home directory: {error:?}"), + }; +} + +fn init_node(config: &NodeConfig) -> EyreResult<()> { + // Sets the default configuration for the node + let node_name: &str = config.name.as_str(); + let node_home: &str = config.home.as_str(); + + let server_port: &str = &config.server_port.to_string(); + let swarm_port: &str = &config.swarm_port.to_string(); + + // create the home directory if it doesnt exist + if !Path::new(node_home).is_dir() { + // Make the Node home directory + make_direcory(node_home); + } + + let mut command: Command = build_command( + node_name, + node_home, + Some(server_port), + Some(swarm_port), + false, + ); + + let child: Output = command.output()?; // Execute the command and get the output + + display_command_output(child); + + Ok(()) // Return the output (stdout, stderr, and exit status) +} + +async fn start_node(node_name: &str, node_home: &str) -> EyreResult<()> { + let mut command: Command = build_command(node_name, node_home, None, None, true); + let child: Output = command.output()?; + + display_command_output(child); + Ok(()) +} + +impl RootCommand { + pub async fn run(self) -> EyreResult<()> { + // Fetch the nodes configuration + let data = NodeData::get_node_data(); + + let coordinator = data.coordinator; + let admin = data.admin; + + match self.action.as_str() { + "init-coordinator" => { + println!("Initializing coordinator...\n"); + init_node(&coordinator) + } + "init-node" => { + println!("Initializing node...\n"); + init_node(&admin) + } + "start-coordinator" => { + println!("Running coordinator...\n"); + + let name: &str = coordinator.name.as_str(); + let home: &str = coordinator.home.as_str(); + + start_node(name, home).await + } + "start-node" => { + println!("Running node...\n"); + + let name: &str = admin.name.as_str(); + let home: &str = admin.home.as_str(); + + start_node(name, home).await + } + _ => { + println!("Unknown command..."); + Ok(()) + } + } + } +} + +impl NodeData { + fn get_node_data() -> NodeData { + // Sets the contents of the configuration file to a String + let contents = match fs::read_to_string(CONFIG_FILE_PATH) { + Ok(c) => c, + Err(_) => { + eprintln!("Could not read file `{}`", CONFIG_FILE_PATH); + exit(1); + } + }; + + // Deserializes the String into a type (NodeData) + let node_data: NodeData = match toml::from_str(&contents) { + Ok(nd) => nd, + Err(_) => { + // Write `msg` to `stderr`. + eprintln!("Unable to load data from `{}`", CONFIG_FILE_PATH); + // Exit the program with exit code `1`. + exit(1); + } + }; + + return node_data; + } +} diff --git a/crates/merow/src/main.rs b/crates/merow/src/main.rs new file mode 100644 index 000000000..54f44b3ad --- /dev/null +++ b/crates/merow/src/main.rs @@ -0,0 +1,13 @@ +#![allow(warnings)] + +use crate::cli::RootCommand; +use clap::Parser; +use eyre::Result as EyreResult; + +mod cli; + +#[tokio::main] +async fn main() -> EyreResult<()> { + let command = RootCommand::parse(); + command.run().await +}