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:
parent
3bba7d1634
commit
ccec3f80dc
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue