cli: add "debug" subcommand

This writes out information that may be useful when debugging Radicle
remotely on a user's machine. Things like versions of programs, and
values of relevant environment variables.

Signed-off-by: Lars Wirzenius <liw@liw.fi>
This commit is contained in:
Lars Wirzenius 2024-02-05 17:52:28 +02:00 committed by cloudhead
parent 3bba7d1634
commit ccec3f80dc
No known key found for this signature in database
3 changed files with 158 additions and 0 deletions

View File

@ -12,6 +12,8 @@ pub mod rad_clone;
pub mod rad_cob;
#[path = "commands/config.rs"]
pub mod rad_config;
#[path = "commands/debug.rs"]
pub mod rad_debug;
#[path = "commands/diff.rs"]
pub mod rad_diff;
#[path = "commands/follow.rs"]

View File

@ -0,0 +1,149 @@
#![allow(clippy::or_fun_call)]
use std::collections::HashMap;
use std::env;
use std::ffi::OsString;
use std::path::PathBuf;
use std::process::Command;
use anyhow::anyhow;
use serde::Serialize;
use radicle::Profile;
use crate::terminal as term;
use crate::terminal::args::{Args, Help};
pub const NAME: &str = "rad";
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const DESCRIPTION: &str = "Radicle command line interface";
pub const GIT_HEAD: &str = env!("GIT_HEAD");
pub const HELP: Help = Help {
name: "debug",
description: "Write out information to help debug your Radicle node remotely",
version: env!("CARGO_PKG_VERSION"),
usage: r#"
Usage
rad debug
Run this if you are reporting a problem in Radicle. The output is
helpful for Radicle developers to debug your problem remotely. The
output is meant to not include any sensitive information, but
please check it, and then forward to the Radicle developers.
"#,
};
#[derive(Debug)]
pub struct Options {}
impl Args for Options {
fn from_args(_args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
Ok((Options {}, vec![]))
}
}
pub fn run(_options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
match ctx.profile() {
Ok(profile) => debug(&profile),
Err(e) => {
eprintln!("ERROR: {e}");
Err(e)
}
}
}
// Collect information about the local Radicle installation and write
// it out.
fn debug(profile: &Profile) -> anyhow::Result<()> {
let env = HashMap::from_iter(env::vars().filter_map(|(k, v)| {
if k == "RAD_PASSPHRASE" {
Some((k, "<REDACTED>".into()))
} else if k.starts_with("RAD_") || k.starts_with("SSH_") {
Some((k, v))
} else {
None
}
}));
let debug = DebugInfo {
rad_version: VERSION,
radicle_node_version: stdout_of("radicle-node", &["--version"])
.unwrap_or("<unknown>".into()),
git_remote_rad_version: stdout_of("git-remote-rad", &["--version"])
.unwrap_or("<unknown>".into()),
git_version: stdout_of("git", &["--version"]).unwrap_or("<unknown>".into()),
ssh_version: stderr_of("ssh", &["-V"]).unwrap_or("<unknown>".into()),
git_head: GIT_HEAD,
log: LogFile::new(profile.node().join("node.log")),
old_log: LogFile::new(profile.node().join("node.log.old")),
operating_system: std::env::consts::OS,
arch: std::env::consts::ARCH,
env,
};
println!("{}", serde_json::to_string_pretty(&debug).unwrap());
Ok(())
}
#[derive(Debug, Serialize)]
#[allow(dead_code)]
#[serde(rename_all = "camelCase")]
struct DebugInfo {
rad_version: &'static str,
radicle_node_version: String,
git_remote_rad_version: String,
git_version: String,
ssh_version: String,
git_head: &'static str,
log: LogFile,
old_log: LogFile,
operating_system: &'static str,
arch: &'static str,
env: HashMap<String, String>,
}
#[derive(Debug, Serialize)]
#[allow(dead_code)]
#[serde(rename_all = "camelCase")]
struct LogFile {
filename: PathBuf,
exists: bool,
len: Option<u64>,
}
impl LogFile {
fn new(filename: PathBuf) -> Self {
Self {
filename: filename.clone(),
exists: filename.exists(),
len: if let Ok(meta) = filename.metadata() {
Some(meta.len())
} else {
None
},
}
}
}
fn output_of(bin: &str, args: &[&str]) -> anyhow::Result<(String, String)> {
let output = Command::new(bin).args(args).output()?;
if !output.status.success() {
return Err(anyhow!("command failed: {bin:?} {args:?}"));
}
let stdout = String::from_utf8_lossy(&output.stdout).trim().to_string();
let stderr = String::from_utf8_lossy(&output.stderr).trim().to_string();
Ok((stdout, stderr))
}
fn stdout_of(bin: &str, args: &[&str]) -> anyhow::Result<String> {
let (stdout, _) = output_of(bin, args)?;
Ok(stdout)
}
fn stderr_of(bin: &str, args: &[&str]) -> anyhow::Result<String> {
let (_, stderr) = output_of(bin, args)?;
Ok(stderr)
}

View File

@ -152,6 +152,13 @@ fn run_other(exe: &str, args: &[OsString]) -> Result<(), Option<anyhow::Error>>
args.to_vec(),
);
}
"debug" => {
term::run_command_args::<rad_debug::Options, _>(
rad_debug::HELP,
rad_debug::run,
args.to_vec(),
);
}
"follow" => {
term::run_command_args::<rad_follow::Options, _>(
rad_follow::HELP,