Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: rad node
Fintan Halpenny committed 3 years ago
commit ed985f6fe0cdc1a204e28ff3890a8f7963a32921
parent f322305f86ed4a8288f86f861313c51afab80e91
8 files changed +288 -0
modified radicle-cli/src/commands.rs
@@ -28,6 +28,8 @@ pub mod rad_issue;
pub mod rad_ls;
#[path = "commands/merge.rs"]
pub mod rad_merge;
+
#[path = "commands/node.rs"]
+
pub mod rad_node;
#[path = "commands/patch.rs"]
pub mod rad_patch;
#[path = "commands/path.rs"]
modified radicle-cli/src/commands/help.rs
@@ -26,6 +26,7 @@ const COMMANDS: &[Help] = &[
    rad_issue::HELP,
    rad_ls::HELP,
    rad_merge::HELP,
+
    rad_node::HELP,
    rad_patch::HELP,
    rad_path::HELP,
    rad_push::HELP,
added radicle-cli/src/commands/node.rs
@@ -0,0 +1,161 @@
+
use std::ffi::OsString;
+

+
use anyhow::anyhow;
+
use radicle::node::{Address, Node, NodeId};
+

+
use crate::terminal as term;
+
use crate::terminal::args::{Args, Error, Help};
+

+
#[path = "node/control.rs"]
+
mod control;
+
#[path = "node/routing.rs"]
+
mod routing;
+
#[path = "node/tracking.rs"]
+
mod tracking;
+

+
pub const HELP: Help = Help {
+
    name: "node",
+
    description: "Control and query the Radicle Node",
+
    version: env!("CARGO_PKG_VERSION"),
+
    usage: r#"
+
Usage
+

+
    rad node status
+
    rad node (start|stop)
+
    rad node connect <nid> <addr>
+
    rad node routing
+
    rad node tracking [--repos|--nodes]
+

+
Options
+

+
    --help          Print help
+
    --repos         Show the tracked repositories table
+
    --nodes         Show the tracked nodes table
+
"#,
+
};
+

+
pub struct Options {
+
    op: Operation,
+
}
+

+
pub enum Operation {
+
    Connect { nid: NodeId, addr: Address },
+
    Routing,
+
    Start,
+
    Status,
+
    Stop,
+
    Tracking { mode: TrackingMode },
+
}
+

+
#[derive(Default)]
+
pub enum TrackingMode {
+
    #[default]
+
    Repos,
+
    Nodes,
+
}
+

+
#[derive(Default)]
+
pub enum OperationName {
+
    Connect,
+
    Routing,
+
    Start,
+
    #[default]
+
    Status,
+
    Stop,
+
    Tracking,
+
}
+

+
impl Args for Options {
+
    fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
+
        use lexopt::prelude::*;
+

+
        let mut parser = lexopt::Parser::from_args(args);
+
        let mut op: Option<OperationName> = None;
+
        let mut tracking_mode = TrackingMode::default();
+
        let mut nid: Option<NodeId> = None;
+
        let mut addr: Option<Address> = None;
+

+
        while let Some(arg) = parser.next()? {
+
            match arg {
+
                Long("help") => {
+
                    return Err(Error::Help.into());
+
                }
+
                Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
+
                    "connect" => op = Some(OperationName::Connect),
+
                    "routing" => op = Some(OperationName::Routing),
+
                    "start" => op = Some(OperationName::Start),
+
                    "status" => op = Some(OperationName::Status),
+
                    "stop" => op = Some(OperationName::Stop),
+
                    "tracking" => op = Some(OperationName::Tracking),
+

+
                    unknown => anyhow::bail!("unknown operation '{}'", unknown),
+
                },
+
                Value(val) if matches!(op, Some(OperationName::Connect)) => {
+
                    match term::args::nid(&val) {
+
                        Ok(val) => {
+
                            nid = Some(val);
+
                        }
+
                        Err(e1) => match term::args::addr(&val) {
+
                            Ok(val) => {
+
                                addr = Some(val);
+
                            }
+
                            Err(e2) => return Err(anyhow!("'{}' or '{}'", e1, e2)),
+
                        },
+
                    }
+
                }
+
                Long("repos") if matches!(op, Some(OperationName::Tracking)) => {
+
                    tracking_mode = TrackingMode::Repos
+
                }
+
                Long("nodes") if matches!(op, Some(OperationName::Tracking)) => {
+
                    tracking_mode = TrackingMode::Nodes
+
                }
+
                _ => return Err(anyhow!(arg.unexpected())),
+
            }
+
        }
+

+
        let op = match op.unwrap_or_default() {
+
            OperationName::Connect => Operation::Connect {
+
                nid: nid.ok_or_else(|| anyhow!("a NID must be provided"))?,
+
                addr: addr.ok_or_else(|| anyhow!("an address must be provided"))?,
+
            },
+
            OperationName::Routing => Operation::Routing,
+
            OperationName::Start => Operation::Start,
+
            OperationName::Status => Operation::Status,
+
            OperationName::Stop => Operation::Stop,
+
            OperationName::Tracking => Operation::Tracking {
+
                mode: tracking_mode,
+
            },
+
        };
+
        Ok((Options { op }, vec![]))
+
    }
+
}
+

+
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
+
    let profile = ctx.profile()?;
+

+
    match options.op {
+
        Operation::Connect { nid, addr } => {
+
            let mut node = Node::new(profile.socket());
+
            control::connect(&mut node, nid, addr)?
+
        }
+
        Operation::Routing => {
+
            let node = Node::new(profile.socket());
+
            routing::run(&node)?;
+
        }
+
        Operation::Start => control::start()?,
+
        Operation::Status => {
+
            let node = Node::new(profile.socket());
+
            control::status(&node);
+
        }
+
        Operation::Stop => {
+
            let node = Node::new(profile.socket());
+
            control::stop(node)?;
+
        }
+
        Operation::Tracking { mode } => {
+
            let node = Node::new(profile.socket());
+
            tracking::run(&node, mode)?
+
        }
+
    }
+

+
    Ok(())
+
}
added radicle-cli/src/commands/node/control.rs
@@ -0,0 +1,44 @@
+
use radicle::node::{Address, Handle as _, NodeId};
+
use radicle::Node;
+

+
use crate::terminal as term;
+

+
pub fn start() -> anyhow::Result<()> {
+
    todo!()
+
}
+

+
pub fn stop(node: Node) -> anyhow::Result<()> {
+
    let spinner = term::spinner("Stopping the node...");
+
    if let Err(err) = node.shutdown() {
+
        spinner.error(format!("Error occurred while shutting down node: {err}"));
+
    } else {
+
        spinner.finish();
+
    }
+
    Ok(())
+
}
+

+
pub fn connect(node: &mut Node, nid: NodeId, addr: Address) -> anyhow::Result<()> {
+
    let spinner = term::spinner(format!(
+
        "Connecting to {}@{addr}...",
+
        term::format::node(&nid)
+
    ));
+
    if let Err(err) = node.connect(nid, addr.clone()) {
+
        spinner.error(format!(
+
            "Failed to connect to {}@{}: {}",
+
            term::format::node(&nid),
+
            term::format::secondary(addr),
+
            err,
+
        ))
+
    } else {
+
        spinner.finish()
+
    }
+
    Ok(())
+
}
+

+
pub fn status(node: &Node) {
+
    if node.is_running() {
+
        term::success!("The node is {}", term::format::positive("running"));
+
    } else {
+
        term::info!("The node is {}", term::format::negative("stopped"));
+
    }
+
}
added radicle-cli/src/commands/node/routing.rs
@@ -0,0 +1,17 @@
+
use radicle::{node::Handle as _, Node};
+

+
use crate::terminal as term;
+

+
pub fn run(node: &Node) -> anyhow::Result<()> {
+
    let mut t = term::Table::new(term::table::TableOptions::default());
+
    t.push(["RID", "NID"]);
+
    t.push(["---", "---"]);
+
    for (id, node) in node.routing()? {
+
        t.push([
+
            term::format::highlight(id).to_string(),
+
            term::format::node(&node),
+
        ]);
+
    }
+
    t.render();
+
    Ok(())
+
}
added radicle-cli/src/commands/node/tracking.rs
@@ -0,0 +1,49 @@
+
use radicle::node::{tracking, Handle as _};
+
use radicle::prelude::Did;
+
use radicle::Node;
+

+
use crate::terminal as term;
+

+
use super::TrackingMode;
+

+
pub fn run(node: &Node, mode: TrackingMode) -> anyhow::Result<()> {
+
    match mode {
+
        TrackingMode::Repos => print_repos(node)?,
+
        TrackingMode::Nodes => print_nodes(node)?,
+
    }
+
    Ok(())
+
}
+

+
fn print_repos(node: &Node) -> anyhow::Result<()> {
+
    let mut t = term::Table::new(term::table::TableOptions::default());
+
    t.push(["RID", "Scope", "Policy"]);
+
    t.push(["---", "-----", "------"]);
+
    for tracking::Repo { id, scope, policy } in node.tracked_repos()? {
+
        t.push([
+
            term::format::highlight(id.to_string()),
+
            term::format::secondary(scope.to_string()),
+
            term::format::secondary(policy.to_string()),
+
        ])
+
    }
+
    t.render();
+
    Ok(())
+
}
+

+
fn print_nodes(node: &Node) -> anyhow::Result<()> {
+
    let mut t = term::Table::new(term::table::TableOptions::default());
+
    t.push(["DID", "Alias", "Policy"]);
+
    t.push(["---", "-----", "------"]);
+
    for tracking::Node { id, alias, policy } in node.tracked_nodes()? {
+
        t.push([
+
            term::format::highlight(Did::from(id).to_string()),
+
            if alias.is_empty() {
+
                term::format::secondary("n/a".to_string())
+
            } else {
+
                term::format::secondary(alias)
+
            },
+
            term::format::secondary(policy.to_string()),
+
        ]);
+
    }
+
    t.render();
+
    Ok(())
+
}
modified radicle-cli/src/main.rs
@@ -222,6 +222,14 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
                args.to_vec(),
            );
        }
+
        "node" => {
+
            term::run_command_args::<rad_node::Options, _>(
+
                rad_node::HELP,
+
                "Node",
+
                rad_node::run,
+
                args.to_vec(),
+
            );
+
        }
        "patch" => {
            term::run_command_args::<rad_patch::Options, _>(
                rad_patch::HELP,
modified radicle-cli/src/terminal/args.rs
@@ -3,6 +3,7 @@ use std::str::FromStr;

use anyhow::anyhow;
use radicle::crypto;
+
use radicle::node::Address;
use radicle::prelude::{Did, Id, NodeId};

#[derive(thiserror::Error, Debug)]
@@ -95,3 +96,8 @@ pub fn rid(val: &OsString) -> anyhow::Result<Id> {
    let val = val.to_string_lossy();
    Id::from_str(&val).map_err(|_| anyhow!("invalid Repository ID '{}'", val))
}
+

+
pub fn addr(val: &OsString) -> anyhow::Result<Address> {
+
    let val = val.to_string_lossy();
+
    Address::from_str(&val).map_err(|_| anyhow!("invalid address '{}'", val))
+
}