Radish alpha
h
rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5
Radicle Heartwood Protocol & Stack
Radicle
Git
cli: add "debug" subcommand
Merged liw opened 2 years ago

This writes out information that may be useful when debugging Radicle remotely on a user’s machine.

Signed-off-by: Lars Wirzenius liw@liw.fi

3 files changed +158 -0 3bba7d16 ccec3f80
modified radicle-cli/src/commands.rs
@@ -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"]
added radicle-cli/src/commands/debug.rs
@@ -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)
+
}
modified radicle-cli/src/main.rs
@@ -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,