cli: rad node
This adds a rad node subcommand to the CLI. The subcommand aims to fulfill the following requirements: - Checking the node status (rad node status) - Connecting to the node's control socket (rad node connect) - Printing out our routing table (rad node routing) - Printing out our tracking policies (rad node tracking) The following are yet to be implemented: - Starting the node (rad node start) - Stopping the node (rad node stop) Signed-off-by: Fintan Halpenny <fintan.halpenny@gmail.com> X-Clacks-Overhead: GNU Terry Pratchett
This commit is contained in:
parent
f322305f86
commit
ed985f6fe0
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue