/ radicle-cli / src / commands / node.rs
node.rs
  1  use std::ffi::OsString;
  2  use std::time;
  3  
  4  use anyhow::anyhow;
  5  
  6  use radicle::node::{Address, Node, NodeId, PeerAddr, ROUTING_DB_FILE};
  7  use radicle::prelude::Id;
  8  
  9  use crate::terminal as term;
 10  use crate::terminal::args::{Args, Error, Help};
 11  use crate::terminal::Element as _;
 12  
 13  #[path = "node/control.rs"]
 14  mod control;
 15  #[path = "node/events.rs"]
 16  mod events;
 17  #[path = "node/routing.rs"]
 18  mod routing;
 19  #[path = "node/tracking.rs"]
 20  mod tracking;
 21  
 22  pub const HELP: Help = Help {
 23      name: "node",
 24      description: "Control and query the Radicle Node",
 25      version: env!("CARGO_PKG_VERSION"),
 26      usage: r#"
 27  Usage
 28  
 29      rad node status [<option>...]
 30      rad node start [--foreground] [--verbose] [<option>...] [-- <node-option>...]
 31      rad node stop [<option>...]
 32      rad node logs [-n <lines>]
 33      rad node connect <nid>@<addr> [<option>...]
 34      rad node routing [--rid <rid>] [--nid <nid>] [--json] [<option>...]
 35      rad node tracking [--repos | --nodes] [<option>...]
 36      rad node events [--timeout <secs>] [-n <count>] [<option>...]
 37      rad node config
 38  
 39      For `<node-option>` see `radicle-node --help`.
 40  
 41  Start options
 42  
 43      --foreground         Start the node in the foreground
 44      --verbose, -v        Verbose output
 45  
 46  Routing options
 47  
 48      --rid <rid>          Show the routing table entries for the given RID
 49      --nid <nid>          Show the routing table entries for the given NID
 50      --json               Output the routing table as json
 51  
 52  Tracking options
 53  
 54      --repos              Show the tracked repositories table
 55      --nodes              Show the tracked nodes table
 56  
 57  Events options
 58  
 59      --timeout <secs>     How long to wait to receive an event before giving up
 60      --count, -n <count>  Exit after <count> events
 61  
 62  General options
 63  
 64      --help               Print help
 65  "#,
 66  };
 67  
 68  pub struct Options {
 69      op: Operation,
 70  }
 71  
 72  pub enum Operation {
 73      Connect {
 74          addr: PeerAddr<NodeId, Address>,
 75          timeout: time::Duration,
 76      },
 77      Config,
 78      Events {
 79          timeout: time::Duration,
 80          count: usize,
 81      },
 82      Routing {
 83          json: bool,
 84          rid: Option<Id>,
 85          nid: Option<NodeId>,
 86      },
 87      Start {
 88          foreground: bool,
 89          verbose: bool,
 90          options: Vec<OsString>,
 91      },
 92      Logs {
 93          lines: usize,
 94      },
 95      Status,
 96      Sessions,
 97      Stop,
 98      Tracking {
 99          mode: TrackingMode,
100      },
101  }
102  
103  #[derive(Default)]
104  pub enum TrackingMode {
105      #[default]
106      Repos,
107      Nodes,
108  }
109  
110  #[derive(Default, PartialEq, Eq)]
111  pub enum OperationName {
112      Connect,
113      Config,
114      Events,
115      Routing,
116      Logs,
117      Start,
118      #[default]
119      Status,
120      Sessions,
121      Stop,
122      Tracking,
123  }
124  
125  impl Args for Options {
126      fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
127          use lexopt::prelude::*;
128  
129          let mut foreground = false;
130          let mut options = vec![];
131          let mut parser = lexopt::Parser::from_args(args);
132          let mut op: Option<OperationName> = None;
133          let mut tracking_mode = TrackingMode::default();
134          let mut nid: Option<NodeId> = None;
135          let mut rid: Option<Id> = None;
136          let mut json: bool = false;
137          let mut addr: Option<PeerAddr<NodeId, Address>> = None;
138          let mut lines: usize = 60;
139          let mut count: usize = usize::MAX;
140          let mut timeout = time::Duration::MAX;
141          let mut verbose = false;
142  
143          while let Some(arg) = parser.next()? {
144              match arg {
145                  Long("help") | Short('h') => {
146                      return Err(Error::Help.into());
147                  }
148                  Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
149                      "connect" => op = Some(OperationName::Connect),
150                      "events" => op = Some(OperationName::Events),
151                      "logs" => op = Some(OperationName::Logs),
152                      "config" => op = Some(OperationName::Config),
153                      "routing" => op = Some(OperationName::Routing),
154                      "start" => op = Some(OperationName::Start),
155                      "status" => op = Some(OperationName::Status),
156                      "stop" => op = Some(OperationName::Stop),
157                      "tracking" => op = Some(OperationName::Tracking),
158                      "sessions" => op = Some(OperationName::Sessions),
159  
160                      unknown => anyhow::bail!("unknown operation '{}'", unknown),
161                  },
162                  Value(val) if matches!(op, Some(OperationName::Connect)) => {
163                      addr = Some(val.parse()?);
164                  }
165                  Long("rid") if matches!(op, Some(OperationName::Routing)) => {
166                      let val = parser.value()?;
167                      rid = term::args::rid(&val).ok();
168                  }
169                  Long("nid") if matches!(op, Some(OperationName::Routing)) => {
170                      let val = parser.value()?;
171                      nid = term::args::nid(&val).ok();
172                  }
173                  Long("json") if matches!(op, Some(OperationName::Routing)) => json = true,
174                  Long("timeout")
175                      if op == Some(OperationName::Events) || op == Some(OperationName::Connect) =>
176                  {
177                      let val = parser.value()?;
178                      timeout = term::args::seconds(&val)?;
179                  }
180                  Long("count") | Short('n') if matches!(op, Some(OperationName::Events)) => {
181                      let val = parser.value()?;
182                      count = term::args::number(&val)?;
183                  }
184                  Long("repos") if matches!(op, Some(OperationName::Tracking)) => {
185                      tracking_mode = TrackingMode::Repos
186                  }
187                  Long("nodes") if matches!(op, Some(OperationName::Tracking)) => {
188                      tracking_mode = TrackingMode::Nodes;
189                  }
190                  Long("foreground") if matches!(op, Some(OperationName::Start)) => {
191                      foreground = true;
192                  }
193                  Long("verbose") | Short('v') if matches!(op, Some(OperationName::Start)) => {
194                      verbose = true;
195                  }
196                  Short('n') if matches!(op, Some(OperationName::Logs)) => {
197                      lines = parser.value()?.parse()?;
198                  }
199                  Value(val) if matches!(op, Some(OperationName::Start)) => {
200                      options.push(val);
201                  }
202                  _ => return Err(anyhow!(arg.unexpected())),
203              }
204          }
205  
206          let op = match op.unwrap_or_default() {
207              OperationName::Connect => Operation::Connect {
208                  addr: addr.ok_or_else(|| {
209                      anyhow!("an address of the form `<nid>@<host>:<port>` must be provided")
210                  })?,
211                  timeout,
212              },
213              OperationName::Config => Operation::Config,
214              OperationName::Events => Operation::Events { timeout, count },
215              OperationName::Routing => Operation::Routing { rid, nid, json },
216              OperationName::Logs => Operation::Logs { lines },
217              OperationName::Start => Operation::Start {
218                  foreground,
219                  verbose,
220                  options,
221              },
222              OperationName::Status => Operation::Status,
223              OperationName::Sessions => Operation::Sessions,
224              OperationName::Stop => Operation::Stop,
225              OperationName::Tracking => Operation::Tracking {
226                  mode: tracking_mode,
227              },
228          };
229          Ok((Options { op }, vec![]))
230      }
231  }
232  
233  pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
234      let profile = ctx.profile()?;
235      let mut node = Node::new(profile.socket());
236  
237      match options.op {
238          Operation::Connect { addr, timeout } => {
239              control::connect(&mut node, addr.id, addr.addr, timeout)?
240          }
241          Operation::Config => control::config(&node)?,
242          Operation::Sessions => {
243              let sessions = control::sessions(&node)?;
244              if let Some(table) = sessions {
245                  table.print();
246              }
247          }
248          Operation::Events { timeout, count } => {
249              events::run(node, count, timeout)?;
250          }
251          Operation::Routing { rid, nid, json } => {
252              let store =
253                  radicle::node::routing::Table::reader(profile.home.node().join(ROUTING_DB_FILE))?;
254              routing::run(&store, rid, nid, json)?;
255          }
256          Operation::Logs { lines } => control::logs(lines, Some(time::Duration::MAX), &profile)?,
257          Operation::Start {
258              foreground,
259              options,
260              verbose,
261          } => {
262              control::start(node, !foreground, verbose, options, &profile)?;
263          }
264          Operation::Status => {
265              control::status(&node, &profile)?;
266          }
267          Operation::Stop => {
268              control::stop(node)?;
269          }
270          Operation::Tracking { mode } => tracking::run(&profile, mode)?,
271      }
272  
273      Ok(())
274  }