pcli/command/debug.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
use anyhow::Result;
use serde::Serialize;
use std::path::PathBuf;
use std::process::Command;
#[derive(Debug, clap::Subcommand)]
pub enum DebugCmd {
/// Emit debugging info, useful for requesting support
Info,
}
impl DebugCmd {
pub fn offline(&self) -> bool {
true
}
pub fn exec(&self, data_dir: PathBuf) -> Result<()> {
match self {
DebugCmd::Info => {
let debug_info = DebugInfo::new(data_dir);
// Using derived serialization as a cheap Display impl for formatting
// the output. It's human-readable enough when pretty, plus we can parse it.
let d = serde_json::to_string_pretty(&debug_info)?;
println!("{d}");
Ok(())
}
}
}
}
/// Represents a fact sheet about system status, bottling up
/// common support-related questions like "What chain are you on?"
/// or "What version of Tendermint are you running?". Intended to display
/// output via `pcli debug info`, for ease of pasting into chat or issues.
// The DebugInfo struct is only used to print info to stdout,
// so its field names won't be accessed elsewhere; thus allow dead_code.
#[allow(dead_code)]
#[derive(Debug, Serialize)]
pub struct DebugInfo {
/// CometBFT version, if cometbft is found on PATH.
cometbft_version: Option<String>,
/// Tendermint version, if tendermint is found on PATH.
// Preserved for a while during Tendermint -> CometBFT,
// to aid in debugging.
tendermint_version: Option<String>,
/// pd version, if pd is found on PATH.
pd_version: Option<String>,
/// pcli version; baked in at compile time, so will always be present.
pcli_version: String,
/// Platform and architecture info for current host.
uname: Option<String>,
/// Status of directory for storing view info locally.
pcli_data_directory: Option<std::path::PathBuf>,
/// Status of pcli config TOML, containing key material for pcli.
pcli_config_file: Option<std::path::PathBuf>,
}
impl DebugInfo {
pub fn new(data_dir: std::path::PathBuf) -> Self {
let dd = Self::get_pcli_data_directory(data_dir);
Self {
cometbft_version: Self::get_cometbft_version(),
tendermint_version: Self::get_tendermint_version(),
pd_version: Self::get_pd_version(),
pcli_version: Self::get_pcli_version(),
uname: Self::get_uname(),
pcli_data_directory: dd.clone(),
pcli_config_file: Self::get_pcli_config_file(dd),
}
}
/// Attempt to retrieve version info for Tendermint by running
/// `tendermint version`. Depending on deployment, tendermint may not be on the PATH;
/// it may be in container context that `pcli` doesn't have access to. That's OK:
/// we'll just report `None` in that case.
fn get_tendermint_version() -> Option<String> {
let cmd = Command::new("tendermint").args(["version"]).output();
match cmd {
Ok(c) => match std::str::from_utf8(&c.stdout) {
Ok(o) => Some(o.trim_end().to_string()),
Err(_) => None,
},
Err(_) => None,
}
}
/// Attempt to retrieve version info for CometBFT by running
/// `cometbft version`. Depending on deployment, cometbft may not be on the PATH;
/// it may be in container context that `pcli` doesn't have access to. That's OK:
/// we'll just report `None` in that case.
fn get_cometbft_version() -> Option<String> {
let cmd = Command::new("cometbft").args(["version"]).output();
match cmd {
Ok(c) => match std::str::from_utf8(&c.stdout) {
Ok(o) => Some(o.trim_end().to_string()),
Err(_) => None,
},
Err(_) => None,
}
}
/// Return host info, including kernel and architecture. Should work
/// equally well on Linux or macOS; Windows will return None.
fn get_uname() -> Option<String> {
let cmd = Command::new("uname").args(["-a"]).output();
match cmd {
Ok(c) => match std::str::from_utf8(&c.stdout) {
Ok(o) => Some(o.trim_end().to_string()),
Err(_) => None,
},
Err(_) => None,
}
}
/// Return the version for `pcli` baked in at compile time.
fn get_pcli_version() -> String {
env!("CARGO_PKG_VERSION").to_string()
}
/// Attempt to find `pd` on PATH, and return its version number. Depending on deployment,
/// `pd` may not be on the path; it may in a container context elsewhere.
fn get_pd_version() -> Option<String> {
match Command::new("pd").args(["--version"]).output() {
Ok(c) => match std::str::from_utf8(&c.stdout) {
Ok(o) => Some(o.trim_end().to_string()),
Err(_) => None,
},
Err(_) => None,
}
}
/// Check whether data dir, as provided by arg-parsing, exists.
fn get_pcli_data_directory(data_dir: PathBuf) -> Option<PathBuf> {
match data_dir.exists() {
true => Some(data_dir),
false => None,
}
}
/// Check pcli config TOML file exists.
fn get_pcli_config_file(data_dir: Option<PathBuf>) -> Option<PathBuf> {
match data_dir {
Some(dd) => {
let mut k = dd;
k.push("config.toml");
if k.exists() {
Some(k)
} else {
None
}
}
None => None,
}
}
}